Files
DP44/Common/DTS.Common.Serialization/IRIGCH10/Chapter10File.cs
2026-04-17 14:55:32 -04:00

435 lines
21 KiB
C#

using DTS.Common;
using DTS.Common.Utilities;
using DTS.Common.Utilities.Logging;
using DTS.Common.Utils;
using DTS.Serialization.IRIGCH10.Packets;
using System;
using System.Collections.Generic;
using System.IO;
namespace DTS.Serialization.IRIGCH10
{
/// <summary>
/// this class is used to simplify parsing out CH10 data from a binary stream
/// </summary>
public class Chapter10File
{
/// <summary>
/// used to hold transport stream headers as they are read
/// </summary>
private readonly List<ITransportStreamHeader> _transportHeaders = new List<ITransportStreamHeader>();
/// <summary>
/// returns all transport headers that were read, in order they were read
/// </summary>
/// <returns></returns>
public ITransportStreamHeader[] GetTransportHeaders() { return _transportHeaders.ToArray(); }
/// <summary>
/// holds a list of packet headers as they are read
/// </summary>
private readonly List<IPacketHeader> _packets = new List<IPacketHeader>();
/// <summary>
/// returns all packet headers that were read, in the order they were read in
/// </summary>
/// <returns></returns>
public IPacketHeader[] GetPacketHeaders() { return _packets.ToArray(); }
/// <summary>
/// holds any secondary time packets that were read
/// </summary>
private readonly List<ISecondaryTimeFormatHeader> _secondaryTimePackets = new List<ISecondaryTimeFormatHeader>();
/// <summary>
/// returns all secondary time headers in the order they were read in
/// </summary>
/// <returns></returns>
public ISecondaryTimeFormatHeader[] GetSecondaryTimeHeaders() { return _secondaryTimePackets.ToArray(); }
/// <summary>
/// lookup of a packet header that was read in as well as the start of that packet and end of that packet amongst
/// all bytes in the file
/// </summary>
private readonly Dictionary<IPacketHeader, Tuple<long, long>> _packetToByteStartStop = new Dictionary<IPacketHeader, Tuple<long, long>>();
private readonly byte[] _bytes = null;
/// <summary>
/// returns all bytes for a packet (as apposed to just the bytes for the packet header)
/// all bytes returned are in their own buffer, independent of bytes read in
/// </summary>
/// <param name="packet"></param>
/// <returns></returns>
public byte[] GetBytesForPacket(IPacketHeader packet)
{
if (!_packetToByteStartStop.ContainsKey(packet)) { return null; }
var tuple = _packetToByteStartStop[packet];
var length = tuple.Item2 - tuple.Item1;
var bytes = new byte[length];
Buffer.BlockCopy(_bytes, (int)tuple.Item1, bytes, 0, (int)length);
return bytes;
}
public int GoodPackets { get; private set; } = 0;
public int RejectedPackets { get; private set; } = 0;
/// <summary>
/// adds a time packet 1 packet to the byte stream
/// </summary>
private static void InsertTimePacket1(BinaryWriter bw, int seconds, int nanoseconds, ref byte timeSequenceNumber,
int startSeconds, int startNanoSeconds)
{
//time packet 1 must always start on a second boundary, so we need to calulate the nearest start second to our
//actual start time, and all further packets also need to be on that boundary
var time = PTP1588Timestamps.UnitTimeStampToDateTimeLocal((decimal)startSeconds, (decimal)startNanoSeconds).ToUniversalTime();
var currentTime = PTP1588Timestamps.UnitTimeStampToDateTimeLocal((decimal)seconds, (decimal)nanoseconds).ToUniversalTime();
var newTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, currentTime.Second);
var tickDelta = time.Ticks - newTime.Ticks;
var newRTC = GetRTC(tickDelta);
var timePacket = new TimePacketFormat1(timeSequenceNumber, newTime, newRTC, nanoseconds, seconds);
timeSequenceNumber++;
bw.Write(timePacket.GetBytes());
}
/// <summary>
/// describes a function to get the next Int16 ADC sample from a binary file
/// </summary>
/// <param name="channelIdx">the index of the channel among channels in the test</param>
/// <returns></returns>
public delegate short GetNextSampleDelegate(int channelIdx);
/// <summary>
/// returns the number of samples for the channel
/// </summary>
/// <param name="channelIndex">the index of the channel among channels in the test</param>
/// <returns></returns>
public delegate long GetChannelLengthDelegate(int channelIndex);
/// <summary>
/// writes an output PCM export file
/// </summary>
public static void WriteFilePCM(string tmats, GetNextSampleDelegate getNextSample,
GetChannelLengthDelegate getChannelLength,
int totalChannels,
int nanoseconds, int seconds, double sampleRate,
bool includeSecondaryHeader,
string fileName,
TickEventHandler tickEventHandler,
object tickObject)
{
var startNanoSeconds = nanoseconds;
var startSeconds = seconds;
if (System.IO.File.Exists(fileName))
{
FileUtils.DeleteFileOrMove(fileName, APILogger.Log);
}
using (var ms = new FileStream(fileName, FileMode.OpenOrCreate))
{
using (var bw = new BinaryWriter(ms))
{
//simulate writing the transport bytes ... not needed for ch10 file though ...
//first packet should be tmats
var tmatsPacket = new TMATSPacket(nanoseconds, seconds, tmats, includeSecondaryHeader);
bw.Write(tmatsPacket.GetBytes());
var currentSample = 0L;
byte sequenceNumber = 0x01;
byte timeSequenceNumber = 0x01;
var lastSecondTransmitted = 0;
if (!includeSecondaryHeader)
{
//insert first time packet at time second 1
InsertTimePacket1(bw, seconds, nanoseconds, ref timeSequenceNumber, startSeconds, startNanoSeconds);
lastSecondTransmitted = seconds;
}
var channel0Length = getChannelLength(0);
while (currentSample < channel0Length)
{
var newRTC = GetRTC(currentSample, sampleRate);
if (includeSecondaryHeader)
{
var timePacket = new TimePacketFormat2(timeSequenceNumber, false, nanoseconds, seconds, newRTC,
includeSecondaryHeader);
var time = PTP1588Timestamps.UnitTimeStampToDateTimeLocal((decimal)seconds, (decimal)nanoseconds).ToUniversalTime();
timeSequenceNumber++;
bw.Write(timePacket.GetBytes());
}
else
{
if (seconds > lastSecondTransmitted)
{
lastSecondTransmitted = seconds;
InsertTimePacket1(bw, seconds, nanoseconds, ref timeSequenceNumber, startSeconds, startNanoSeconds);
}
}
var pcmPacket = GetPcmPacket(currentSample, getNextSample, channel0Length, totalChannels, nanoseconds, seconds, sampleRate, out var samplesProcessed,
sequenceNumber, includeSecondaryHeader);
bw.Write(pcmPacket.GetBytes());
currentSample += samplesProcessed;
sequenceNumber++;
var elapsedNanoSeconds = samplesProcessed / (decimal)sampleRate * Constants.NANOS_PER_SECOND;
var newNanos = nanoseconds + elapsedNanoSeconds;
if (newNanos > Constants.NANOS_PER_SECOND)
{
while (newNanos >= Constants.NANOS_PER_SECOND)
{
seconds++;
newNanos -= Constants.NANOS_PER_SECOND;
}
}
nanoseconds = Convert.ToInt32(Math.Truncate(newNanos));
tickEventHandler?.Invoke(tickObject, 50D + 50D * currentSample / channel0Length);
}
}
}
}
public static void WriteFileAnalog(string tmats, GetNextSampleDelegate getNextSample,
GetChannelLengthDelegate getChannelLength,
int totalChannels,
int nanoseconds, int seconds, double sampleRate,
bool includeSecondaryHeader,
string fileName,
TickEventHandler tickEventHandler,
object tickObject)
{
var startNanoSeconds = nanoseconds;
var startSeconds = seconds;
if (System.IO.File.Exists(fileName))
{
FileUtils.DeleteFileOrMove(fileName, APILogger.Log);
}
using (var ms = new FileStream(fileName, FileMode.OpenOrCreate))
{
using (var bw = new BinaryWriter(ms))
{
//simulate writing the transport bytes ... not needed for ch10 file though ...
//first packet should be tmats
var tmatsPacket = new TMATSPacket(nanoseconds, seconds, tmats, includeSecondaryHeader);
bw.Write(tmatsPacket.GetBytes());
var currentSample = 0L;
byte sequenceNumber = 0x01;
byte timeSequenceNumber = 0x01;
var lastSecondTransmitted = 0;
if (!includeSecondaryHeader)
{
//insert first time packet at time second 1
InsertTimePacket1(bw, seconds, nanoseconds, ref timeSequenceNumber, startSeconds, startNanoSeconds);
lastSecondTransmitted = seconds;
}
var channel0Length = getChannelLength(0);
while (currentSample < channel0Length)
{
var newRTC = GetRTC(currentSample, sampleRate);
if (includeSecondaryHeader)
{
var timePacket = new TimePacketFormat2(timeSequenceNumber, false, nanoseconds, seconds, newRTC,
includeSecondaryHeader);
var time = PTP1588Timestamps.UnitTimeStampToDateTimeLocal((decimal)seconds, (decimal)nanoseconds).ToUniversalTime();
var newTime = new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second);
timeSequenceNumber++;
bw.Write(timePacket.GetBytes());
}
else
{
if (seconds > lastSecondTransmitted)
{
lastSecondTransmitted = seconds;
InsertTimePacket1(bw, seconds, nanoseconds, ref timeSequenceNumber, startSeconds, startNanoSeconds);
}
}
var analogPacket = GetAnalogPacket(currentSample, getNextSample, channel0Length, totalChannels, nanoseconds, seconds, sampleRate, out var samplesProcessed,
sequenceNumber, includeSecondaryHeader);
bw.Write(analogPacket.GetBytes());
currentSample += samplesProcessed;
sequenceNumber++;
var elapsedNanoSeconds = samplesProcessed / (decimal)sampleRate * Constants.NANOS_PER_SECOND;
var newNanos = nanoseconds + elapsedNanoSeconds;
if (newNanos > Constants.NANOS_PER_SECOND)
{
while (newNanos >= Constants.NANOS_PER_SECOND)
{
seconds++;
newNanos -= Constants.NANOS_PER_SECOND;
}
}
nanoseconds = Convert.ToInt32(Math.Truncate(newNanos));
tickEventHandler?.Invoke(tickObject, 50D + 50D * currentSample / channel0Length);
}
}
}
}
/// <summary>
/// returns a PCM data packet with the next samples (targetting 100ms of data)
/// </summary>
private static PCMDataPacket GetPcmPacket(long currentSample, GetNextSampleDelegate getNextSample,
long channelLength, int totalChannels, int nanoseconds,
int seconds, double sampleRate, out long samplesProcessed, byte sequenceNumber, bool includeSecondaryHeader)
{
var newRTC = GetRTC(currentSample, sampleRate);
var numSamples = channelLength;
numSamples -= currentSample;
//we should target 100ms of data per packet, sample rate is in seconds
//so divide by 10 to get number of samples per 100ms
var numSamplesPerPacket = sampleRate / 10;
//if we want more samples than are available, just truncate down to max available
if (numSamples > numSamplesPerPacket)
{
numSamples = Convert.ToInt64(numSamplesPerPacket);
}
var pcmPacket = new PCMDataPacket(nanoseconds, seconds,
getNextSample, totalChannels, channelLength, newRTC, numSamples, currentSample, sequenceNumber, 3,
includeSecondaryHeader);
samplesProcessed = numSamples;
return pcmPacket;
}
private static AnalogDataFormat1Packet GetAnalogPacket(long currentSample, GetNextSampleDelegate getNextSample,
long channelLength, int totalChannels, int nanoseconds,
int seconds, double sampleRate, out long samplesProcessed, byte sequenceNumber, bool includeSecondaryHeader)
{
var newRTC = GetRTC(currentSample, sampleRate);
var numSamples = channelLength;
numSamples -= currentSample;
//we should target 100ms of data per packet
var numSamplesPerPacket = Math.Floor(sampleRate / 10);
if (numSamples > numSamplesPerPacket)
{
numSamples = Convert.ToInt64(numSamplesPerPacket);
}
var analogPacket = new AnalogDataFormat1Packet(nanoseconds, seconds,
getNextSample, totalChannels, channelLength, newRTC, numSamples, currentSample, sequenceNumber, 3,
includeSecondaryHeader);
samplesProcessed = numSamples;
return analogPacket;
}
//for convenience just store relative time counter 10MHZ conversion factor
private const int RTC_PER_SECOND = 10000000;
private static long GetRTC(long currentSample, double sampleRate)
{
var secondsPassed = currentSample / sampleRate;
return AbstractDataPacket.BASE_RTC + Convert.ToInt64(secondsPassed * RTC_PER_SECOND);
}
private static long GetRTC(long tickDelta)
{
var seconds = (double)tickDelta / TimeSpan.TicksPerSecond;
return AbstractDataPacket.BASE_RTC - Convert.ToInt64(seconds * RTC_PER_SECOND);
}
/// <summary>
/// parses out all packets from array of bytes
/// </summary>
/// <param name="bytes"></param>
public Chapter10File(byte[] bytes)
{
_bytes = new byte[bytes.Length];
Buffer.BlockCopy(bytes, 0, _bytes, 0, bytes.Length);
var currentByteIndex = 0L;
var badPackets = 0;
var goodPackets = 0;
while (currentByteIndex < (bytes.Length - PacketHeader.PACKET_HEADER_LENGTH))
{
try
{
currentByteIndex = ReadTransportHeader(bytes, currentByteIndex, out var transportHeader);
var startOfChapter10Packet = currentByteIndex;
currentByteIndex = ReadChapter10PacketHeader(bytes, currentByteIndex, out var packetHeader);
if (packetHeader.PacketSyncPattern != PacketHeader.EXPECTED_SYNC_PATTERN || packetHeader.CheckSum != packetHeader.ComputeCheckSum())
{
badPackets++;
//this packet is invalid, so we need to skip ahead
//skip until we see the next sync pattern, then backstep to the start of the transport bytes, then continue on
currentByteIndex = GetIndexOfNextPacket(bytes, startOfChapter10Packet);
continue;
}
_transportHeaders.Add(transportHeader);
if (packetHeader.SecondaryHeaderPresent)
{
ReadChapter10SecondHeaderTimeFormat(bytes, startOfChapter10Packet + PacketHeader.PACKET_HEADER_LENGTH, out var secondaryTimeHeader);
_secondaryTimePackets.Add(secondaryTimeHeader);
}
_packets.Add(packetHeader);
_packetToByteStartStop[packetHeader] = new Tuple<long, long>(startOfChapter10Packet, currentByteIndex);
goodPackets++;
}
catch (Exception ex)
{
throw new Exception($"Hit exception processing file at byte: {currentByteIndex}, {ex.Message}", ex);
}
}
GoodPackets = goodPackets;
RejectedPackets = badPackets;
}
/// <summary>
/// determines the next valid packet starting location after the provided starting spot
/// this is used to skip over bad data to the next good packet
/// </summary>
/// <param name="bytes"></param>
/// <param name="startIndex"></param>
/// <returns></returns>
public static long GetIndexOfNextPacket(byte[] bytes, long startIndex)
{
for (var i = startIndex; i < bytes.Length - 1; i++)
{
if (BitConverter.ToUInt16(bytes, (int)i) == PacketHeader.EXPECTED_SYNC_PATTERN)
{
return i;
}
}
//if we got here there are no more valid packets, you are done.
return bytes.Length;
}
/// <summary>
/// reads a secondary time header from provided bytes and starting spot
/// </summary>
/// <param name="bytes"></param>
/// <param name="start"></param>
/// <param name="secondaryTimeFormat"></param>
private void ReadChapter10SecondHeaderTimeFormat(byte[] bytes, long start, out ISecondaryTimeFormatHeader secondaryTimeFormat)
{
var headerBytes = new byte[SecondaryTimeFormatHeader.SECONDARY_TIME_HEADER_LENGTH];
Array.Copy(bytes, start, headerBytes, 0, SecondaryTimeFormatHeader.SECONDARY_TIME_HEADER_LENGTH);
secondaryTimeFormat = new SecondaryTimeFormatHeader(headerBytes);
}
/// <summary>
/// reads a transport header from provided bytes and starting spot
/// </summary>
/// <param name="bytes"></param>
/// <param name="startIndex"></param>
/// <param name="transportHeader"></param>
/// <returns></returns>
private long ReadTransportHeader(byte[] bytes, long startIndex, out TransportStreamHeader transportHeader)
{
var transportHeaderBytes = new byte[4];
Array.Copy(bytes, startIndex, transportHeaderBytes, 0, 4);
transportHeader = new TransportStreamHeader(transportHeaderBytes);
return startIndex + 4L;
}
/// <summary>
/// reads a Packet from stream of bytes at given starting spot
/// </summary>
/// <param name="bytes"></param>
/// <param name="startIndex"></param>
/// <param name="packetHeader"></param>
/// <returns></returns>
public static long ReadChapter10PacketHeader(byte[] bytes, long startIndex, out IPacketHeader packetHeader)
{
var headerBytes = new byte[PacketHeader.PACKET_HEADER_LENGTH];
Array.Copy(bytes, startIndex, headerBytes, 0, PacketHeader.PACKET_HEADER_LENGTH);
packetHeader = new PacketHeader(headerBytes);
return startIndex + packetHeader.PacketLength;
}
}
}