Clarify Well source health and process clocks
This commit is contained in:
parent
980f185104
commit
2fa008aee4
7 changed files with 25 additions and 41 deletions
|
|
@ -108,8 +108,6 @@ public sealed class MimirAsioStreamSource : IMimirMultiplexedStreamSource
|
|||
|
||||
started.Set();
|
||||
var sampleBuffer = new float[Math.Max(1, maxFrames)];
|
||||
long firstDeviceTimestampNs = 0;
|
||||
long firstCanonicalEndNs = 0;
|
||||
while (!cancellation.IsCancellationRequested)
|
||||
{
|
||||
if (!Native.Read(
|
||||
|
|
@ -134,16 +132,7 @@ public sealed class MimirAsioStreamSource : IMimirMultiplexedStreamSource
|
|||
var bytes = new byte[frameCount * sizeof(float)];
|
||||
var arrivalNs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
|
||||
var durationNs = checked((long)Math.Ceiling(frameCount * 1_000_000_000.0 / Math.Max(1, sampleRate)));
|
||||
if (timestampNs > 0 && firstDeviceTimestampNs == 0)
|
||||
{
|
||||
firstDeviceTimestampNs = timestampNs;
|
||||
firstCanonicalEndNs = arrivalNs;
|
||||
}
|
||||
|
||||
var endNs = timestampNs > 0 && firstDeviceTimestampNs > 0
|
||||
? checked(firstCanonicalEndNs + (timestampNs - firstDeviceTimestampNs))
|
||||
: arrivalNs;
|
||||
var startNs = Math.Max(0, endNs - durationNs);
|
||||
var startNs = Math.Max(0, arrivalNs - durationNs);
|
||||
MemoryMarshal.AsBytes(sampleBuffer.AsSpan(0, frameCount)).CopyTo(bytes);
|
||||
samples.Enqueue(new MimirStreamSample(
|
||||
sourceId,
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ public sealed class MimirFfmpegPcmAudioStreamSource : IMimirStreamSource
|
|||
offset += read;
|
||||
}
|
||||
|
||||
var timestampNs = StopwatchTicksToNs(Stopwatch.GetTimestamp());
|
||||
var timestampNs = UtcNowNs();
|
||||
samples.Enqueue(new MimirStreamSample(
|
||||
Descriptor.SourceId,
|
||||
Descriptor.Kind,
|
||||
|
|
@ -137,10 +137,7 @@ public sealed class MimirFfmpegPcmAudioStreamSource : IMimirStreamSource
|
|||
_ => 0,
|
||||
};
|
||||
|
||||
private static long StopwatchTicksToNs(long ticks)
|
||||
{
|
||||
return checked((long)(ticks * (1_000_000_000.0 / Stopwatch.Frequency)));
|
||||
}
|
||||
private static long UtcNowNs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ public sealed class MimirFfmpegRawVideoStreamSource : IMimirStreamSource
|
|||
offset += read;
|
||||
}
|
||||
|
||||
var timestampNs = StopwatchTicksToNs(Stopwatch.GetTimestamp());
|
||||
var timestampNs = UtcNowNs();
|
||||
var videoFrame = new MimirVideoFrameDescriptor(
|
||||
Options.Width,
|
||||
Options.Height,
|
||||
|
|
@ -152,10 +152,7 @@ public sealed class MimirFfmpegRawVideoStreamSource : IMimirStreamSource
|
|||
};
|
||||
}
|
||||
|
||||
private static long StopwatchTicksToNs(long ticks)
|
||||
{
|
||||
return checked((long)(ticks * (1_000_000_000.0 / Stopwatch.Frequency)));
|
||||
}
|
||||
private static long UtcNowNs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -118,8 +118,8 @@ public sealed class MimirFrameEventProcessStreamSource : IMimirStreamSource
|
|||
|
||||
var timestampNs = frameEvent.TimestampNs > 0
|
||||
? frameEvent.TimestampNs
|
||||
: StopwatchTicksToNs(Stopwatch.GetTimestamp());
|
||||
var arrivalNs = StopwatchTicksToNs(Stopwatch.GetTimestamp());
|
||||
: UtcNowNs();
|
||||
var arrivalNs = UtcNowNs();
|
||||
var byteLength = Math.Max(0, frameEvent.ByteLength);
|
||||
var payloadHandle = frameEvent.NativeHandle;
|
||||
var sampleKind = frameEvent.IsAudioBlock ? MimirStreamKind.Audio : Descriptor.Kind;
|
||||
|
|
@ -271,10 +271,7 @@ public sealed class MimirFrameEventProcessStreamSource : IMimirStreamSource
|
|||
};
|
||||
}
|
||||
|
||||
private static long StopwatchTicksToNs(long ticks)
|
||||
{
|
||||
return checked((long)(ticks * (1_000_000_000.0 / Stopwatch.Frequency)));
|
||||
}
|
||||
private static long UtcNowNs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -74,8 +74,7 @@ public sealed class MimirProcessStreamSource : IMimirStreamSource
|
|||
Array.Resize(ref buffer, read);
|
||||
}
|
||||
|
||||
var now = Stopwatch.GetTimestamp();
|
||||
var timestampNs = StopwatchTicksToNs(now);
|
||||
var timestampNs = UtcNowNs();
|
||||
samples.Enqueue(new MimirStreamSample(
|
||||
Descriptor.SourceId,
|
||||
Descriptor.Kind,
|
||||
|
|
@ -102,10 +101,7 @@ public sealed class MimirProcessStreamSource : IMimirStreamSource
|
|||
}
|
||||
}
|
||||
|
||||
private static long StopwatchTicksToNs(long ticks)
|
||||
{
|
||||
return checked((long)(ticks * (1_000_000_000.0 / Stopwatch.Frequency)));
|
||||
}
|
||||
private static long UtcNowNs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000L;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1558,11 +1558,18 @@ internal static class MimirWellSnapshot
|
|||
TryReadAnonymousString(error, "SourceId", "sourceId") is { } id &&
|
||||
string.Equals(id, descriptor.SourceId, StringComparison.Ordinal));
|
||||
var elapsedMs = Math.Max(0.0, (DateTimeOffset.UtcNow - startedAt).TotalMilliseconds);
|
||||
var latestAgeMs = latest.HasValue
|
||||
? Math.Round(Math.Max(0.0, (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000.0 - latest.Value.TimestampNs) / 1_000_000.0), 1)
|
||||
: Math.Round(elapsedMs, 1);
|
||||
var status = errored
|
||||
? "create-error"
|
||||
: samples > 0
|
||||
? "live"
|
||||
: "created-no-samples";
|
||||
: samples > 0 && latestAgeMs > 2_000.0
|
||||
? "stalled"
|
||||
: samples > 0
|
||||
? "live"
|
||||
: descriptor.Origin == MimirStreamOrigin.Network
|
||||
? "waiting-network"
|
||||
: "waiting-device-samples";
|
||||
|
||||
return new
|
||||
{
|
||||
|
|
@ -1574,15 +1581,15 @@ internal static class MimirWellSnapshot
|
|||
reason = status switch
|
||||
{
|
||||
"create-error" => "source creation failed; see sourceErrors",
|
||||
"created-no-samples" => "source was created but has not emitted a sample",
|
||||
"stalled" => "source emitted samples earlier but latest sample is stale",
|
||||
"waiting-network" => "network source listener is configured but no producer samples have arrived",
|
||||
"waiting-device-samples" => "local device source is configured but has not emitted a sample",
|
||||
_ => "samples arriving",
|
||||
},
|
||||
buffers = matchingBuffers.Length,
|
||||
samples,
|
||||
latestTimestampNs = latest?.TimestampNs ?? 0L,
|
||||
latestAgeMs = latest.HasValue
|
||||
? Math.Round(Math.Max(0.0, (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000_000.0 - latest.Value.TimestampNs) / 1_000_000.0), 1)
|
||||
: Math.Round(elapsedMs, 1),
|
||||
latestAgeMs,
|
||||
latestByteLength = latest?.ByteLength ?? 0,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -367,3 +367,4 @@
|
|||
{"ts": "2026-06-02T19:17:54+01:00", "type": "decision", "status": "ok", "note": "Online Verse shakedown: canonical timestamps are owned by MimirCanonicalClockMapper, which now rebases streams whose projected source clock drifts more than 10s from arrival after ASIO/device resets; MimirSynchronizationHub no longer reapplies raw clock-domain corrections to already-canonical buffers. Fensalir Eve surface now mirrors sibling daemon PID liveness so Odin can lower relay, recorder, Well, Move sync, and Fensalir statuses without taking control authority."}
|
||||
{"ts": "2026-06-02T23:49:45+01:00", "type": "decision", "status": "ok", "note": "Mimir Well/Fensalir live repair: rolling-buffer authority now uses canonical arrival UTC timestamps for ASIO/native camera samples while preserving device timestamps as calibration evidence; Fensalir CultCache persistence is best-effort so a Windows File.Replace sharing failure cannot kill the live Eve daemon."}
|
||||
{"ts": "2026-06-03T04:13:02+01:00", "type": "implementation", "status": "ok", "note": "2026-06-03: ASIO readiness repair: Mimir now derives ASIO block times from native device timestamps, preserves local-epoch timestamps in the canonical mapper, sorts planner samples chronologically, keeps frames visible when overlap pinches, and lets the latency controller chase FutureSampleOnly slices downward. Live Well reached repeated 6/6 Ready frames at roughly 75-80 ms presentation delay with ASIO distances reported around 32-36 ms."}
|
||||
{"ts": "2026-06-03T04:40:48+01:00", "type": "implementation", "status": "ok", "note": "2026-06-03: Source health and process-clock repair: FFmpeg/process/frame-event sources now use UTC arrival timestamps instead of Stopwatch-domain timestamps; Well sourceHealth distinguishes live, stalled, waiting-network, and waiting-device-samples. Live verification after restart reached repeated 6/6 Ready frames, while streamPressure exposed the next bottleneck: inline WebSocket body lane published ~66 MB across 53 Well snapshots at ~82 ms average publish cost."}
|
||||
|
|
|
|||
Loading…
Reference in a new issue