435 lines
21 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|