init
This commit is contained in:
434
Common/DTS.Common.Serialization/IRIGCH10/Chapter10File.cs
Normal file
434
Common/DTS.Common.Serialization/IRIGCH10/Chapter10File.cs
Normal file
@@ -0,0 +1,434 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user