using System.Buffers.Binary; namespace TravelEar.Core; /// One Offset observation the Helper sends back to the mod. /// The capture timestamp that travelled with the Sink frame (the mod's Stopwatch ticks at OpusEncoder.Encode). /// When the frame's first sample leaves the Helper's endpoint, in the same clock (both processes read the machine's performance counter). public readonly record struct OffsetReport(long CaptureTimestamp, long RenderTimestamp); /// /// Framing for the return pipe (TravelEar.Sink.Back): fixed 20-byte little-endian records, /// magic then the two timestamps. Shared by the Helper (writer) and the mod (reader). /// public static class OffsetReportFrame { /// "TEOF" (TravelEar Offset) read as a little-endian uint32. public const uint Magic = 0x464F4554; public const int Size = 4 + 8 + 8; // [impl->REQ-OFFSET-MEASURE] public static void Write(Span destination, in OffsetReport report) { if (destination.Length < Size) throw new ArgumentException("Destination too small.", nameof(destination)); BinaryPrimitives.WriteUInt32LittleEndian(destination, Magic); BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(4), report.CaptureTimestamp); BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(12), report.RenderTimestamp); } /// Parses one record. False if the bytes are too short or carry no magic. public static bool TryRead(ReadOnlySpan source, out OffsetReport report) { report = default; if (source.Length < Size) return false; if (BinaryPrimitives.ReadUInt32LittleEndian(source) != Magic) return false; report = new OffsetReport( BinaryPrimitives.ReadInt64LittleEndian(source.Slice(4)), BinaryPrimitives.ReadInt64LittleEndian(source.Slice(12))); return true; } /// Writes one record to a stream. must hold bytes. public static void WriteTo(Stream stream, in OffsetReport report, byte[] scratch) { Write(scratch, report); stream.Write(scratch, 0, Size); } /// /// Reads exactly one record. False on a clean end-of-stream before a record starts; throws /// on a bad magic or a record cut by end-of-stream. /// public static bool TryReadFrom(Stream stream, out OffsetReport report, byte[] scratch) { report = default; var total = 0; while (total < Size) { var n = stream.Read(scratch, total, Size - total); if (n <= 0) break; total += n; } if (total == 0) return false; if (total < Size) throw new InvalidDataException("Offset stream ended inside a record."); if (!TryRead(scratch, out report)) throw new InvalidDataException("Invalid Offset record."); return true; } }