diff --git a/skyscraper8/Atsc/A322/AtscPlpBasebandParser.cs b/skyscraper8/Atsc/A322/AtscPlpBasebandParser.cs index e4e1587..3a5f7f8 100644 --- a/skyscraper8/Atsc/A322/AtscPlpBasebandParser.cs +++ b/skyscraper8/Atsc/A322/AtscPlpBasebandParser.cs @@ -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 diff --git a/skyscraper8/Atsc/A322/L1BasicSignalling.cs b/skyscraper8/Atsc/A322/L1BasicSignalling.cs new file mode 100644 index 0000000..ccb6cc4 --- /dev/null +++ b/skyscraper8/Atsc/A322/L1BasicSignalling.cs @@ -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 + } +} diff --git a/skyscraper8/Atsc/A322/L1DetailSignalling.cs b/skyscraper8/Atsc/A322/L1DetailSignalling.cs new file mode 100644 index 0000000..354825a --- /dev/null +++ b/skyscraper8/Atsc/A322/L1DetailSignalling.cs @@ -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) + { + } + } +} diff --git a/skyscraper8/Atsc/A324/BasebandPacketDataStreamReceiver.cs b/skyscraper8/Atsc/A324/BasebandPacketDataStreamReceiver.cs index cb59528..3e64a01 100644 --- a/skyscraper8/Atsc/A324/BasebandPacketDataStreamReceiver.cs +++ b/skyscraper8/Atsc/A324/BasebandPacketDataStreamReceiver.cs @@ -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; diff --git a/skyscraper8/Atsc/A324/PreambleData.cs b/skyscraper8/Atsc/A324/PreambleData.cs new file mode 100644 index 0000000..acc789d --- /dev/null +++ b/skyscraper8/Atsc/A324/PreambleData.cs @@ -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; } + } +} diff --git a/skyscraper8/Atsc/A330/AlpPacket.cs b/skyscraper8/Atsc/A330/AlpPacket.cs new file mode 100644 index 0000000..d327cbd --- /dev/null +++ b/skyscraper8/Atsc/A330/AlpPacket.cs @@ -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 + } +}