Implemented ATSC 3 L1BasicSignalling and L1DetailSignalling
All checks were successful
🚀 Pack skyscraper8 / make-zip (push) Successful in 44s

This commit is contained in:
feyris-tan 2026-05-25 22:48:07 +02:00
parent f4193297d2
commit ef903cba96
8 changed files with 509 additions and 15 deletions

View File

@ -1,4 +1,5 @@
using skyscraper5.Skyscraper.IO;
using skyscraper5.Ietf.Rfc971;
using skyscraper5.Skyscraper.IO;
using skyscraper8.Atsc.A330;
using System;
using System.Collections.Generic;
@ -103,7 +104,8 @@ namespace skyscraper8.Atsc.A322
}
else
{
throw new NotImplementedException("Concatenation Header not yet implemented.");
stateMachineState = 7;
break;
}
}
stateMachineState = 4;
@ -125,6 +127,45 @@ namespace skyscraper8.Atsc.A322
DeliverPacket(currentAlpPacket, neededBuffer);
stateMachineState = 1;
break;
case 7: //Read Concatenation Header first byte.
byte[] concatHeaderFirstByteBuffer = new byte[1];
int readResult = ms.Read(concatHeaderFirstByteBuffer, 0, 1);
if (readResult == 0)
break;
currentAlpPacket.ConcatLengthMsb = (concatHeaderFirstByteBuffer[0] & 0xf0) >> 4;
currentAlpPacket.Count = (concatHeaderFirstByteBuffer[0] & 0x0e) >> 1;
currentAlpPacket.SIF = (concatHeaderFirstByteBuffer[0] & 0x01) != 0;
int neededLength = 12 * currentAlpPacket.Count;
if ((currentAlpPacket.Count & 1) == 0)
neededLength += 4;
neededLength /= 8;
neededBufferOffset = 0;
stateMachineState = 8;
break;
case 8: //Get Concatenation Header Lengths
int readSuccesful3 = ms.Read(neededBuffer, neededBufferOffset, neededBuffer.Length - neededBufferOffset);
neededBufferOffset += readSuccesful3;
if (neededBufferOffset == neededBuffer.Length)
{
stateMachineState = 9;
}
break;
case 9: //Parse Concatenation Header Lengths
currentAlpPacket.ParseConcatenationHeaderLengths(neededBuffer);
if (currentAlpPacket.PacketType == AlpPacketType.LinkLayerSignalling)
{
throw new NotImplementedException("Concatenated Link Layer Signalling Packets in ATSC 3.0 not yet supported.");
}
currentAlpPacket.Length2 = (currentAlpPacket.ConcatLengthMsb << 11) | currentAlpPacket.Length;
neededBufferOffset = 0;
neededBuffer = new byte[currentAlpPacket.Length2];
stateMachineState = 5;
break;
default:
throw new NotImplementedException(String.Format("ATSC PLP Baseband Parser State machine state #{0}", stateMachineState));
}
@ -133,7 +174,7 @@ namespace skyscraper8.Atsc.A322
private void DeliverPacket(AlpPacket? currentAlpPacket, byte[]? neededBuffer)
{
Console.WriteLine("PacketType = {0} \t Length = {1}", currentAlpPacket.PacketType, neededBuffer.Length);
Console.WriteLine("PacketType = {0} \t Length = {1} (first bytes = {2:X2},{3:X2},{4:X2})", currentAlpPacket.PacketType, neededBuffer.Length, neededBuffer[0], neededBuffer[1], neededBuffer[2]);
}
internal void OnSyncLoss()
@ -145,6 +186,7 @@ namespace skyscraper8.Atsc.A322
private int stateMachineState;
private byte[] neededBuffer;
private int neededBufferOffset;
}
internal enum OfiDescription

View File

@ -39,8 +39,8 @@ namespace skyscraper8.Atsc.A322
L1DetailAdditionalParityMode = bitstream.ReadBitsAsByte(2);
L1DetailTotalCells = bitstream.ReadBitsAsUint(19);
FirstSubMimo = bitstream.ReadBitAsBoolean();
FirstSubMiso = bitstream.ReadBitsAsByte(2);
FirstSubFftSize = bitstream.ReadBitsAsByte(2);
FirstSubMiso = (MisoSignallingFormat)bitstream.ReadBitsAsByte(2);
FirstSubFftSize = (FftSizeSignallingFormat)bitstream.ReadBitsAsByte(2);
FirstSubReducedCarries = bitstream.ReadBitsAsByte(3);
FirstSubGuardInterval = bitstream.ReadBitsAsByte(4);
FirstSubNumOfdmSymbols = bitstream.ReadBitsAsUint(11);
@ -49,6 +49,9 @@ namespace skyscraper8.Atsc.A322
FirstSubSbsFirst = bitstream.ReadBitAsBoolean();
FirstSubSbsLast = bitstream.ReadBitAsBoolean();
FirstSubMimoMixed = bitstream.ReadBitAsBoolean();
byte[] bytes = bitstream.ReadBitsAsByteArray(47); //reserved
uint crc = bitstream.ReadBitsAsUint(32);
Valid = true;
}
@ -71,8 +74,8 @@ namespace skyscraper8.Atsc.A322
public byte L1DetailAdditionalParityMode { get; }
public uint L1DetailTotalCells { get; }
public bool FirstSubMimo { get; }
public byte FirstSubMiso { get; }
public byte FirstSubFftSize { get; }
public MisoSignallingFormat FirstSubMiso { get; }
public FftSizeSignallingFormat FirstSubFftSize { get; }
public byte FirstSubReducedCarries { get; }
public byte FirstSubGuardInterval { get; private set; }
public uint FirstSubNumOfdmSymbols { get; }
@ -98,4 +101,5 @@ namespace skyscraper8.Atsc.A322
AceOnly,
TrAndAce
}
}

View File

@ -1,4 +1,5 @@
using skyscraper5.Skyscraper;
using skyscraper5.Skyscraper.IO;
using System;
using System.Collections.Generic;
using System.Linq;
@ -9,8 +10,307 @@ namespace skyscraper8.Atsc.A322
{
internal class L1DetailSignalling : Validatable
{
public L1DetailSignalling(byte[] bytes)
public L1DetailSignalling(byte[] bytes, L1BasicSignalling basicSignalling)
{
InBitStream bitStream = new InBitStream(bytes);
this.Version = new Version(bitStream.ReadBitsAsByte(4), 0);
byte numRf = bitStream.ReadBitsAsByte(3);
RfIds = new ushort[numRf];
for (int i = 1; i < numRf; i++)
{
RfIds[i] = bitStream.ReadBitsAsUInt16(16);
bitStream.ReadBitsAsByte(3); //reserved
}
if (basicSignalling.TimeInfoFlag != 0)
{
uint secs = 0;
int msecs = 0;
int microsecs = 0;
int nanosecs = 0;
secs = bitStream.ReadBitsAsUint(32);
msecs = bitStream.ReadBitsAsUInt16(10);
if ((int)basicSignalling.TimeInfoFlag != 1)
{
microsecs = bitStream.ReadBitsAsUInt16(10);
if ((int)basicSignalling.TimeInfoFlag != 2)
{
nanosecs = bitStream.ReadBitsAsUInt16(10);
}
}
}
Subframes = new L1Subframe[basicSignalling.NumSubframes];
for (int i = 0; i < basicSignalling.NumSubframes; i++)
{
L1Subframe child = new L1Subframe();
Subframes[i] = child;
if (i > 0)
{
child.Mimo = bitStream.ReadBitAsBoolean();
child.Miso = (MisoSignallingFormat)bitStream.ReadBitsAsByte(2);
child.FftSize = (FftSizeSignallingFormat)bitStream.ReadBitsAsByte(2);
child.ReducedCarriers = bitStream.ReadBitsAsByte(3);
child.GuardInterval = bitStream.ReadBitsAsByte(3);
child.NumOfdmSymbols = bitStream.ReadBitsAsUInt16(11);
child.ScatteredPilotPatern = bitStream.ReadBitsAsByte(5);
child.ScatteredPilotBoost = bitStream.ReadBitsAsByte(3);
child.SbsFirst = bitStream.ReadBitAsBoolean();
child.SbsLast = bitStream.ReadBitAsBoolean();
}
if (basicSignalling.NumSubframes > 0)
{
child.SubframeMultiplex = bitStream.ReadBitAsBoolean();
}
child.FrequencyInterleaver = bitStream.ReadBitAsBoolean();
if (((i == 0) && (basicSignalling.FirstSubSbsFirst || basicSignalling.FirstSubSbsLast)) || ((i > 0) && (child.SbsFirst | child.SbsLast)))
{
child.SbsNullCells = bitStream.ReadBitsAsUInt16(13);
}
byte numPlp = bitStream.ReadBitsAsByte(6);
child.Plps = new L1SubframePlp[numPlp];
for (int j = 0; j < numPlp; j++)
{
L1SubframePlp plp = new L1SubframePlp();
child.Plps[j] = plp;
plp.Id = bitStream.ReadBitsAsByte(6);
plp.LlsFlag = bitStream.ReadBitAsBoolean();
plp.Layer = bitStream.ReadBitsAsByte(2);
plp.Start = bitStream.ReadBitsAsUint(24);
plp.Size = bitStream.ReadBitsAsUint(24);
plp.ScramblerType = bitStream.ReadBitsAsByte(2);
plp.FecType = (PlpFecTypeSignallingFormat)bitStream.ReadBitsAsByte(4);
if (plp.NeedModCod())
{
plp.Mod = (PlpModSignallingFormat)bitStream.ReadBitsAsByte(4);
plp.Cod = (PlpCodSignallingFormat)bitStream.ReadBitsAsByte(4);
}
plp.TiMode = (PlpTiModeSignallingFormat)bitStream.ReadBitsAsByte(2);
if (plp.TiMode == PlpTiModeSignallingFormat.NoTimeInterleaving)
{
plp.FecBlockStart = bitStream.ReadBitsAsUInt16(15);
}
else if (plp.TiMode == PlpTiModeSignallingFormat.ConvolutionalTimeInterleaving)
{
plp.CtiFecBlockStart = bitStream.ReadBitsAsUint(22);
}
if (numRf > 0)
{
byte numChannelBonded = bitStream.ReadBitsAsByte(3);
if (numChannelBonded > 0)
{
plp.ChannelBondingFormat = (PlpChannelBondingFormatSignallingFormat)bitStream.ReadBitsAsByte(2);
plp.BondedRfId = new byte[numChannelBonded];
for (byte k = 0; k < numChannelBonded; k++)
{
plp.BondedRfId[k] = bitStream.ReadBitsAsByte(3);
}
}
}
if ((i == 0 && basicSignalling.FirstSubMimo) || (i > 0 && child.Mimo))
{
plp.MimoStreamCombining = bitStream.ReadBitAsBoolean();
plp.MimoIqInterleaving = bitStream.ReadBitAsBoolean();
plp.MimoPh = bitStream.ReadBitAsBoolean();
}
if (plp.Layer == 0)
{
plp.Type = bitStream.ReadBitAsBoolean();
if (plp.Type)
{
plp.NumSubslices = bitStream.ReadBitsAsUInt16(14);
plp.SubsliceInterval = bitStream.ReadBitsAsUint(24);
}
if (((plp.TiMode == PlpTiModeSignallingFormat.ConvolutionalTimeInterleaving) || (plp.TiMode == PlpTiModeSignallingFormat.HybridTimeInterleaving) && (plp.Mod == 0)))
{
plp.TiExtendedInterleaving = bitStream.ReadBitAsBoolean();
}
if (plp.TiMode == PlpTiModeSignallingFormat.ConvolutionalTimeInterleaving)
{
plp.CtiDepth = bitStream.ReadBitsAsByte(3);
plp.CtiStartRow = bitStream.ReadBitsAsUInt16(11);
}
else if (plp.TiMode == PlpTiModeSignallingFormat.HybridTimeInterleaving)
{
plp.HtiInterSubframe = bitStream.ReadBitAsBoolean();
byte htiNumTiBlocks = bitStream.ReadBitsAsByte(4);
plp.HtiNumFecBlocksMax = bitStream.ReadBitsAsUInt16(12);
if (!plp.HtiInterSubframe)
{
plp.HtiNumFecBlocksMax = bitStream.ReadBitsAsUInt16(12);
}
else
{
plp.HtiNumFecBlocks = new ushort[htiNumTiBlocks];
for (byte k = 0; k < htiNumTiBlocks; k++)
{
plp.HtiNumFecBlocks[k] = bitStream.ReadBitsAsUInt16(12);
}
}
plp.HtiCellInterleaver = bitStream.ReadBitAsBoolean();
}
}
else
{
plp.LdmInjectionLevel = bitStream.ReadBitsAsByte(5);
}
}
}
Bsid = bitStream.ReadBitsAsUInt16(16);
for (int i = 0; i < basicSignalling.NumSubframes; i++)
{
if (i > 0)
{
Subframes[i].MimoMixed = bitStream.ReadBitAsBoolean();
}
if ((i == 0 && basicSignalling.FirstSubMimoMixed) || (i > 0 || Subframes[i].MimoMixed))
{
for (int j = 0; j < Subframes[i].Plps.Length; j++)
{
Subframes[i].Plps[j].Mimo = bitStream.ReadBitAsBoolean();
if (Subframes[i].Plps[j].Mimo)
{
Subframes[i].Plps[j].MimoStreamCombining = bitStream.ReadBitAsBoolean();
Subframes[i].Plps[j].MimoIqInterleaving = bitStream.ReadBitAsBoolean();
Subframes[i].Plps[j].MimoPh = bitStream.ReadBitAsBoolean();
}
}
}
}
Valid = true;
}
public Version Version { get; }
public ushort[] RfIds { get; private set; }
public L1Subframe[] Subframes { get; private set; }
public ushort Bsid { get; }
}
internal class L1SubframePlp
{
public byte Id { get; internal set; }
public bool LlsFlag { get; internal set; }
public byte Layer { get; internal set; }
public uint Start { get; internal set; }
public uint Size { get; internal set; }
public byte ScramblerType { get; internal set; }
public PlpFecTypeSignallingFormat FecType { get; internal set; }
public PlpModSignallingFormat Mod { get; internal set; }
public PlpCodSignallingFormat Cod { get; internal set; }
public PlpTiModeSignallingFormat TiMode { get; internal set; }
public ushort FecBlockStart { get; internal set; }
public uint CtiFecBlockStart { get; internal set; }
public PlpChannelBondingFormatSignallingFormat ChannelBondingFormat { get; internal set; }
public byte[] BondedRfId { get; internal set; }
public bool MimoStreamCombining { get; internal set; }
public bool MimoIqInterleaving { get; internal set; }
public bool MimoPh { get; internal set; }
public bool Type { get; internal set; }
public ushort NumSubslices { get; internal set; }
public uint SubsliceInterval { get; internal set; }
public bool TiExtendedInterleaving { get; internal set; }
public byte CtiDepth { get; internal set; }
public ushort CtiStartRow { get; internal set; }
public bool HtiInterSubframe { get; internal set; }
public ushort HtiNumFecBlocksMax { get; internal set; }
public ushort[] HtiNumFecBlocks { get; internal set; }
public bool HtiCellInterleaver { get; internal set; }
public byte LdmInjectionLevel { get; internal set; }
public bool Mimo { get; internal set; }
internal bool NeedModCod()
{
throw new NotImplementedException();
}
}
class L1Subframe
{
public bool Mimo { get; internal set; }
public MisoSignallingFormat Miso { get; internal set; }
public FftSizeSignallingFormat FftSize { get; internal set; }
public byte ReducedCarriers { get; internal set; }
public byte GuardInterval { get; internal set; }
public ushort NumOfdmSymbols { get; internal set; }
public byte ScatteredPilotPatern { get; internal set; }
public byte ScatteredPilotBoost { get; internal set; }
public bool SbsFirst { get; internal set; }
public bool SbsLast { get; internal set; }
public bool SubframeMultiplex { get; internal set; }
public bool FrequencyInterleaver { get; internal set; }
public ushort SbsNullCells { get; internal set; }
public L1SubframePlp[] Plps { get; internal set; }
public bool MimoMixed { get; internal set; }
}
enum MisoSignallingFormat
{
NoMiso,
_64Coefficients,
_256Coefficients,
}
enum FftSizeSignallingFormat
{
_8k,
_16k,
_32k,
}
enum PlpFecTypeSignallingFormat
{
Bch16kLdpc,
Bch64kLdpc,
Crc16kLdpc,
Crc64kLdpc,
_16kLdpc,
_64kLdpc,
}
enum PlpModSignallingFormat
{
Qpsk,
_16QAM,
_64QAM,
_256QAM,
_1024QAM,
_4096Q
}
enum PlpCodSignallingFormat
{
_2_15,
_3_15,
_4_15,
_5_15,
_6_15,
_7_15,
_8_15,
_9_15,
_10_15,
_11_15,
_12_15,
_13_15,
}
enum PlpTiModeSignallingFormat
{
NoTimeInterleaving,
ConvolutionalTimeInterleaving,
HybridTimeInterleaving
}
enum PlpChannelBondingFormatSignallingFormat
{
PlainChannelBonding,
SnrAveragedChannelBonding
}
}

View File

@ -115,7 +115,7 @@ namespace skyscraper8.Atsc.A324
}
else if (plp == 65)
{
throw new NotImplementedException("desynced timing and management");
DesyncTimingAndManagement();
}
else if (plp <= 63)
{
@ -126,7 +126,18 @@ namespace skyscraper8.Atsc.A324
throw new NotImplementedException(String.Format("Unknown PLP: {0}", plp));
}
}
plpSequenceNumbers[plp] = rtpPacket.RtpSequenceNumber;
plpSequenceNumbers[plp]++;
}
private TimingAndManagementPacket timingAndManagement;
private void DesyncTimingAndManagement()
{
if (timingAndManagement != null)
{
timingAndManagement.Valid = false;
}
}
private void DesyncPreamble()
@ -153,7 +164,7 @@ namespace skyscraper8.Atsc.A324
private void HandleTimingAndManagementData(byte[] rtpPayload)
{
throw new NotImplementedException();
timingAndManagement = new TimingAndManagementPacket(rtpPayload);
}
private PreambleData preambleData;

View File

@ -30,7 +30,7 @@ namespace skyscraper8.Atsc.A324
}
long detailLength = expectedLength - 25;
DetailSignalling = new L1DetailSignalling(ms.ReadBytes(detailLength));
DetailSignalling = new L1DetailSignalling(ms.ReadBytes(detailLength), BasicSignalling);
Valid = DetailSignalling.Valid;
}

View File

@ -0,0 +1,128 @@
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.A324
{
internal class TimingAndManagementPacket : Validatable
{
public TimingAndManagementPacket(byte[] rtpPayload)
{
//STRUCTURE DATA
InBitStream bitStream = new InBitStream(rtpPayload);
ushort length = bitStream.ReadBitsAsUInt16(16);
if (length != rtpPayload.Length)
{
Valid = false;
return;
}
byte versionMajor = bitStream.ReadBitsAsByte(4);
byte versionMinor = bitStream.ReadBitsAsByte(4);
this.Version = new Version(versionMajor, versionMinor);
MajLogRepCntPre = bitStream.ReadBitsAsByte(4);
MajLogRepCntTim = bitStream.ReadBitsAsByte(4);
byte bootstrapMajor = bitStream.ReadBitsAsByte(4);
byte bootstrapMinor = bitStream.ReadBitsAsByte(4);
this.Bootstrap = new Version(bootstrapMajor, bootstrapMinor);
MinTimeToNext = bitStream.ReadBitsAsByte(5);
SystemBandwidth = bitStream.ReadBitsAsByte(2);
BsrCoefficient = bitStream.ReadBitsAsByte(7);
PreambleStructure = bitStream.ReadBitsAsByte(8);
EaWakeup = bitStream.ReadBitsAsByte(2);
byte numEmissionTim = bitStream.ReadBitsAsByte(6);
byte numXmtrsInGroupMinus1 = bitStream.ReadBitsAsByte(6);
XmtrGroupNum = bitStream.ReadBitsAsByte(7);
MajLogOverride = bitStream.ReadBitsAsByte(3);
NumMisoFiltCodes = bitStream.ReadBitsAsByte(2);
TxCarrierOffset = bitStream.ReadBitsAsByte(2);
MimoFlag = bitStream.ReadBitAsBoolean();
bitStream.ReadBitsAsByte(6); //reserved
//BOOTSTRAP TIMING DATA
EmissionTim = new TimeSpan[numEmissionTim];
for (int i = 0; i < numEmissionTim; i++)
{
uint seconds = bitStream.ReadBitsAsUint(32);
uint nanoseconds = bitStream.ReadBitsAsUint(32);
long microsecs = nanoseconds / 1000;
EmissionTim[i] = new TimeSpan(0, 0, 0, (int)seconds, 0, (int)microsecs);
}
//PER TRANSMITTER DATA
for (int i = 0; i <= numXmtrsInGroupMinus1; i++)
{
int intMimoFlag = MimoFlag ? 1 : 0;
for (int j = 0; j <= intMimoFlag; j++)
{
if (PerTransmitterData == null)
PerTransmitterData = new PerTransmitPolarizationData[numXmtrsInGroupMinus1 + 1][];
if (PerTransmitterData[i] == null)
PerTransmitterData[i] = new PerTransmitPolarizationData[intMimoFlag + 1];
PerTransmitPolarizationData child = new PerTransmitPolarizationData();
PerTransmitterData[i][j] = child;
child.XmtrId = bitStream.ReadBitsAsUInt16(13);
if (j == 0)
{
child.TxTimeOffset = bitStream.ReadBitsAsUInt16(16);
}
child.TxIdInjectionLevel = bitStream.ReadBitsAsUint(4);
child.MisoFiltCodeIndex = bitStream.ReadBitsAsByte(2);
}
if (!MimoFlag)
{
bitStream.ReadBitsAsUint(29);
}
else
{
bitStream.ReadBitsAsUint(10);
}
}
//PACKET RELEASE TIME
byte pktRlsSeconds = bitStream.ReadBitsAsByte(4);
ushort pktRlsAMilliseconds = bitStream.ReadBitsAsUInt16(10);
PktRls = new TimeSpan(0, 0, 0, pktRlsSeconds, pktRlsAMilliseconds);
bitStream.SkipBits(2);
//ERROR CHECK DATA
ushort crc16 = bitStream.ReadBitsAsUInt16(16);
}
public Version Version { get; }
public byte MajLogRepCntPre { get; }
public byte MajLogRepCntTim { get; }
public Version Bootstrap { get; }
public byte MinTimeToNext { get; }
public byte SystemBandwidth { get; }
public byte BsrCoefficient { get; }
public byte PreambleStructure { get; }
public byte EaWakeup { get; }
public byte XmtrGroupNum { get; }
public byte MajLogOverride { get; }
public byte NumMisoFiltCodes { get; }
public byte TxCarrierOffset { get; }
public bool MimoFlag { get; }
public TimeSpan[] EmissionTim { get; }
public PerTransmitPolarizationData[][] PerTransmitterData { get; }
public TimeSpan PktRls { get; }
}
public class PerTransmitPolarizationData
{
public ushort XmtrId { get; internal set; }
public ushort TxTimeOffset { get; internal set; }
public uint TxIdInjectionLevel { get; internal set; }
public byte MisoFiltCodeIndex { get; internal set; }
}
}

View File

@ -32,6 +32,15 @@ namespace skyscraper8.Atsc.A330
public bool? HeaderMode { get; }
public bool? SegmentationConcatenation { get; }
public int Length { get; }
public int ConcatLengthMsb { get; internal set; }
public int Count { get; internal set; }
public bool SIF { get; internal set; }
public int Length2 { get; internal set; }
internal void ParseConcatenationHeaderLengths(byte[]? neededBuffer)
{
throw new NotImplementedException();
}
}
enum AlpPacketType

View File

@ -110,12 +110,12 @@ namespace skyscraper5.Skyscraper.IO
public byte[] ReadBitsAsByteArray(uint v)
{
byte[] result = new byte[v / 8];
byte[] result = new byte[(v / 8) + 1];
int offset = 0;
while (v > 0)
{
uint nextSize = Math.Min(8, v);
result[offset++] = ReadBitsAsByte(8);
byte nextSize = Math.Min((byte)8, (byte)v);
result[offset++] = ReadBitsAsByte(nextSize);
v -= nextSize;
}
return result;
@ -125,7 +125,7 @@ namespace skyscraper5.Skyscraper.IO
public long GetBitsAvailable()
{
long result = _wrapped.GetAvailableBytes();
long result = _wrapped.GetAvailableBytes() * 8;
result += (8 - bitPointer);
return result;
}