Add Raven Eve network ingest setup

This commit is contained in:
Meta Vens 2026-05-30 22:47:21 +01:00
commit 19ed000cd8
12 changed files with 609 additions and 6 deletions

View file

@ -0,0 +1,98 @@
{
"bufferSeconds": 5.0,
"audioSync": {
"mode": "chirp-only",
"referenceSourceId": "asio-ch2",
"calibrationGain": 2.0,
"watermarkGain": 0.035,
"calibrationModelPath": "../calibration/chirp-bin/scarlett-artifact.json",
"complexContourChannelModelPath": "../calibration/bioacoustic/complex-contour-channel-model.json",
"bioacousticWitnessProfileId": "canary-packet-trill",
"enableComplexContourRuntime": true
},
"streams": [
{
"sourceId": "focusrite-asio",
"kind": "Audio",
"origin": "LocalDevice",
"enabled": true,
"adapter": "asio",
"command": "../native/asio_capture/build/Release/mimir_asio_capture.dll",
"sampleRate": 192000,
"acceptSourceIds": [
"asio-ch0",
"asio-ch1",
"asio-ch2",
"asio-ch3"
],
"sourceLabels": {
"asio-ch0": "Focusrite Input 1",
"asio-ch1": "Focusrite Input 2",
"asio-ch2": "Focusrite Loopback 1",
"asio-ch3": "Focusrite Loopback 2"
}
},
{
"sourceId": "raven-display",
"displayName": "Raven screen capture",
"clockDomainId": "raven-sync",
"kind": "Video",
"origin": "Network",
"enabled": true,
"adapter": "ffmpeg-rawvideo",
"command": "ffmpeg",
"width": 1920,
"height": 1080,
"pixelFormat": "Bgra8",
"arguments": [
"-hide_banner",
"-nostdin",
"-loglevel",
"warning",
"-fflags",
"nobuffer",
"-flags",
"low_delay",
"-i",
"srt://0.0.0.0:5200?mode=listener&latency=120000&timeout=5000000",
"-an",
"-f",
"rawvideo",
"-pix_fmt",
"bgra",
"-"
]
},
{
"sourceId": "eve-camera",
"displayName": "Eve camera capture",
"clockDomainId": "eve-camera",
"kind": "Video",
"origin": "Network",
"enabled": true,
"adapter": "ffmpeg-rawvideo",
"command": "ffmpeg",
"width": 1280,
"height": 720,
"pixelFormat": "Bgra8",
"arguments": [
"-hide_banner",
"-nostdin",
"-loglevel",
"warning",
"-fflags",
"nobuffer",
"-flags",
"low_delay",
"-i",
"srt://0.0.0.0:5201?mode=listener&latency=120000&timeout=5000000",
"-an",
"-f",
"rawvideo",
"-pix_fmt",
"bgra",
"-"
]
}
]
}

View file

@ -216,6 +216,12 @@ a named invariant that the native runtime cannot protect yet.
The display feed remains derived evidence, not an independent timing owner.
`Mimir.BufferSmoke --synchronized-buffer-planner-smoke` proves the aligned
local-camera, Raven-display, loopback, and mic buffer shape.
- `MimirFfmpegRawVideoStreamSource` is the current network-pixel adapter for
Raven/Eve ingest. FFmpeg owns SRT receive/decode, Mimir owns exact raw-video
frame reads and synchronized buffer samples. `config/mimir-runtime.raven-eve.example.json`
listens for Raven screen capture on port `5200` and Eve camera capture on
port `5201`. `Mimir.BufferSmoke --ffmpeg-rawvideo-source-smoke` proves the
local raw-video adapter without Raven or Eve online.
- `MimirPresentationControlState` owns operator intent for the Fensalir program
surface: video feed visibility/solo/opacity/layer order, audio mute/solo/gain,
and global LUT preset selection. The `Mimir Program` UI panel is deliberately

View file

@ -31,15 +31,23 @@ Practical shape:
applies that clock-domain correction to the `raven-display` video buffer.
5. Fensalir receives aligned buffer slices and still owns composition.
The disabled example in `config/mimir-runtime.asio.example.json` tags the
network display stream with `clockDomainId: raven-sync`. The display receiver is
expected to emit frame-event metadata with Raven source timestamps and frame
identity. The Scarlett channel carrying Raven's sync audio is the evidence path
that earns the `raven-sync` timing correction.
`config/mimir-runtime.raven-eve.example.json` is the active setup shape for the
network producer pass:
- `raven-display` listens for Raven screen capture on SRT port `5200`, decodes
through FFmpeg, and emits BGRA video samples into the runtime buffer path.
- `eve-camera` listens for Eve camera capture on SRT port `5201`, using the same
raw-video adapter.
The older disabled `frame-events` example in
`config/mimir-runtime.asio.example.json` remains diagnostic only. It can witness
source timestamps and frame identity, but it does not carry pixels. The Scarlett
channel carrying Raven's sync audio is still the evidence path that earns the
`raven-sync` timing correction.
Smoke proof:
```powershell
dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj -- --synchronized-buffer-planner-smoke
dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj -- --ffmpeg-rawvideo-source-smoke
```

101
docs/raven-eve-ingest.md Normal file
View file

@ -0,0 +1,101 @@
# Raven And Eve Ingest
## Objective
Ingest Raven screen capture and Eve camera capture into Mimir as ordinary
network video sources, then let the existing synchronization hub place those
samples in rolling buffers beside Scarlett audio and local camera sources.
## Authority Map
- Owner: `Mimir.Runtime` owns decoded video samples and rolling-buffer
placement.
- Inputs: Raven sends H.264/MPEG-TS over SRT to Starfire port `5200`; Eve sends
camera video over SRT to Starfire port `5201`; Scarlett/ASIO carries audio and
sync evidence.
- Outputs: `raven-display` and `eve-camera` emit `MimirStreamSample` frames with
BGRA payloads and `MimirVideoFrameDescriptor` metadata.
- Derived state: network arrival timestamps are capture metadata only. Raven
audio-in-Scarlett remains the timing witness for global alignment.
- Forbidden writers: OBS, FFmpeg sender clocks, and SRT arrival time do not own
final sync truth.
- Cut line: no custom transport daemon and no second compositor path until this
FFmpeg/SRT edge fails a concrete invariant.
## Starfire Receiver
Use the Raven/Eve runtime config:
```powershell
$env:MIMIR_RUNTIME_CONFIG = "E:\Projects\Mimir\config\mimir-runtime.raven-eve.example.json"
dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj -- --seconds 5
```
When both senders are active, require sample arrival:
```powershell
$env:MIMIR_RUNTIME_CONFIG = "E:\Projects\Mimir\config\mimir-runtime.raven-eve.example.json"
dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj -- --seconds 5 --require-samples
```
The config starts two FFmpeg listener processes:
- `raven-display`: `srt://0.0.0.0:5200?mode=listener&latency=120000&timeout=5000000`
- `eve-camera`: `srt://0.0.0.0:5201?mode=listener&latency=120000&timeout=5000000`
Each listener decodes to raw BGRA frames on stdout. Mimir reads exact frame
boundaries and stores those frames in the normal synchronization buffers.
## Raven Sender
Run this on Raven from the logged-in desktop session:
```powershell
E:\Projects\Mimir\scripts\start-raven-screen-capture-sender.ps1 -TargetHost 192.168.1.66 -Port 5200
```
The script uses `gdigrab` plus `h264_nvenc`, then calls Starfire's SRT listener.
Do not launch the capture itself through a blind SSH session unless the desktop
capture path has been proven there; `gdigrab` captures the interactive Windows
desktop, and service/session boundaries can turn screen capture into a black
box with excellent logs.
Raven sync signal: route Raven audio or a Raven-generated chirp into the
Scarlett alongside the microphone. Mimir should treat that audio evidence as
the authority for aligning Raven video with the rest of the program.
## Eve Sender Status
Eve is configured as a receiver lane now, and SSH is reachable, but the actual
camera producer is not implemented in this repo yet. The existing Eve app is a
view/control surface for receiving Starfire video and sending touch events; it
is not currently an AVFoundation camera broadcaster. No `ffmpeg` or obvious
camera-capture helper is installed on the iPad path checked during setup.
Current next options:
- Add AVFoundation capture to Eve and send H.264 over SRT or MPEG-TS/TCP to
Starfire. This keeps Eve as the source owner and matches the network-producer
model, but needs the iPad online and a native sender cut.
- Use a known iOS camera streaming app that can publish RTSP, SRT, NDI, or
MPEG-TS, then point the `eve-camera` FFmpeg listener/receiver at that stream.
This is fastest for a room test, but delegates camera authority to an app
outside the Mimir/Eve codebase.
- Capture Eve over USB/Continuity on Starfire and expose it as a local camera
source. This is operationally simple if Windows sees the device, but Eve stops
being a networked producer and the clock-domain model changes.
The coherent default is the AVFoundation sender, because it gives Mimir one
clean producer per physical source and leaves sync ownership explicit.
## Smoke Test
The raw-video source has a local synthetic smoke that does not require Raven or
Eve:
```powershell
dotnet run --project .\src\Mimir.BufferSmoke\Mimir.BufferSmoke.csproj -- --ffmpeg-rawvideo-source-smoke
```
That smoke starts FFmpeg with `testsrc2`, decodes BGRA frames through stdout,
and verifies the sample metadata and payload size that Mimir receives.

View file

@ -82,6 +82,12 @@ probes can emit per-frame JSON metadata so Fensalir sees real sensor cadence in
the rolling buffers while the direct ABI driver is being cut. It does not carry
pixels and does not own the final six-camera hot path. Multi-camera probes are
one process with declared accepted source ids, not one process per camera.
The `ffmpeg-rawvideo` adapter is the live network-pixel edge for Raven/Eve
setup: FFmpeg owns SRT receive/decode and writes exact raw frames to stdout;
`MimirFfmpegRawVideoStreamSource` owns frame-boundary reads, BGRA/NV12 payload
geometry, sample descriptors, and insertion into the normal synchronization
path. `config/mimir-runtime.raven-eve.example.json` declares `raven-display`
on SRT port `5200` and `eve-camera` on SRT port `5201`.
## OBS Bridge Utility
@ -387,6 +393,11 @@ shape is explicit: Raven controls both display pixels and an audio timing signal
routed into Scarlett, so Scarlett-decoded Raven audio evidence can earn a
`raven-sync` correction that is applied to the `raven-display` buffer. Network
arrival timestamps remain metadata, not timing authority.
The first operational ingest config is `config/mimir-runtime.raven-eve.example.json`:
Raven screen capture and Eve camera capture arrive over SRT, decode to raw BGRA
through FFmpeg, and enter the same rolling-buffer model as local sources. Eve's
receiver lane exists; the iPad-side camera producer still needs either a native
AVFoundation sender or an explicitly chosen external camera-streaming app.
`MimirPresentationControlState` now owns the Fensalir program-control intent:
video feed visibility/solo/opacity/layer order, audio mute/solo/gain, and
global LUT preset selection. The `Mimir Program` panel exposes those controls

View file

@ -116,6 +116,13 @@ Get-Content .\state\evidence.jsonl -Tail 8
static viewer belongs to the root GameCult site at
`https://gamecult.org/livestream` and reads
`https://streampixels.gamecult.org/mimir/live/hls/mimir.m3u8`.
- Raven/Eve ingest now has a network-pixel receiver config:
`config/mimir-runtime.raven-eve.example.json` starts FFmpeg-backed raw-video
listeners for `raven-display` on SRT port `5200` and `eve-camera` on port
`5201`. `scripts/start-raven-screen-capture-sender.ps1` launches Raven desktop
capture with `gdigrab` plus NVENC. Eve's Mimir receiver lane exists, but the
iPad-side camera producer is still an open cut: add native AVFoundation send
support or deliberately choose an external iOS streaming app.
- Faust/native DSP owns hot audio alignment, separation, spatialization, and
synchronized stems.
- OBS receives final program surfaces; it does not own synchronization.

View file

@ -0,0 +1,66 @@
param(
[string]$TargetHost = "192.168.1.66",
[int]$Port = 5200,
[int]$Width = 1920,
[int]$Height = 1080,
[int]$Framerate = 30,
[string]$FfmpegPath = "ffmpeg",
[string]$Source = "desktop",
[string]$VideoBitrate = "12000k",
[string]$LogRoot = "C:\Meta\Mimir\logs",
[switch]$DryRun
)
$ErrorActionPreference = "Stop"
New-Item -ItemType Directory -Force -Path $LogRoot | Out-Null
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$stdoutLog = Join-Path $LogRoot "raven-screen-srt-$timestamp.out.log"
$stderrLog = Join-Path $LogRoot "raven-screen-srt-$timestamp.err.log"
$endpoint = "srt://${TargetHost}:${Port}?mode=caller&latency=120000"
$videoSize = "${Width}x${Height}"
$gop = [Math]::Max(1, $Framerate * 2)
$arguments = @(
"-hide_banner",
"-nostdin",
"-loglevel", "warning",
"-f", "gdigrab",
"-framerate", $Framerate.ToString(),
"-video_size", $videoSize,
"-i", $Source,
"-an",
"-c:v", "h264_nvenc",
"-preset", "p4",
"-tune", "ll",
"-b:v", $VideoBitrate,
"-maxrate", $VideoBitrate,
"-bufsize", "24000k",
"-pix_fmt", "yuv420p",
"-g", $gop.ToString(),
"-f", "mpegts",
$endpoint
)
Write-Host "Raven screen sender:"
Write-Host " ffmpeg: $FfmpegPath"
Write-Host " target: $endpoint"
Write-Host " source: $Source $videoSize@$Framerate"
Write-Host " stdout: $stdoutLog"
Write-Host " stderr: $stderrLog"
if ($DryRun)
{
Write-Host ($FfmpegPath + " " + ($arguments -join " "))
exit 0
}
$process = Start-Process `
-FilePath $FfmpegPath `
-ArgumentList $arguments `
-WindowStyle Hidden `
-RedirectStandardOutput $stdoutLog `
-RedirectStandardError $stderrLog `
-PassThru
Write-Host "Started Raven screen sender pid=$($process.Id)"

View file

@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Globalization;
using System.Numerics;
using System.Security.Cryptography;
using System.Text.Json;
@ -169,6 +170,11 @@ if (args.Any(arg => string.Equals(arg, "--mf-gpu-driver-smoke", StringComparison
return RunMfGpuDriverSmoke(args);
}
if (args.Any(arg => string.Equals(arg, "--ffmpeg-rawvideo-source-smoke", StringComparison.OrdinalIgnoreCase)))
{
return RunFfmpegRawVideoSourceSmoke(args);
}
if (args.Any(arg => string.Equals(arg, "--mimir-spectrum-upload-smoke", StringComparison.OrdinalIgnoreCase)))
{
return RunMimirSpectrumUploadSmoke();
@ -1039,6 +1045,69 @@ static int RunMfGpuDriverSmoke(string[] args)
return 1;
}
static int RunFfmpegRawVideoSourceSmoke(string[] args)
{
var ffmpeg = ParseStringOption(args, "--ffmpeg", "ffmpeg");
var sourceId = ParseStringOption(args, "--source-id", "ffmpeg-rawvideo-smoke");
var width = ParseIntOption(args, "--width", 160);
var height = ParseIntOption(args, "--height", 90);
var fps = ParseIntOption(args, "--fps", 10);
var frames = ParseIntOption(args, "--frames", 3);
var timeoutMs = ParseIntOption(args, "--timeout-ms", 3000);
using var source = new MimirFfmpegRawVideoStreamSource(
new MimirStreamDescriptor(sourceId, MimirStreamKind.Video, MimirStreamOrigin.Network),
new MimirFfmpegRawVideoStreamSourceOptions(
ffmpeg,
[
"-hide_banner",
"-nostdin",
"-loglevel",
"error",
"-f",
"lavfi",
"-i",
$"testsrc2=size={width}x{height}:rate={fps}",
"-frames:v",
frames.ToString(CultureInfo.InvariantCulture),
"-f",
"rawvideo",
"-pix_fmt",
"bgra",
"-"
],
width,
height,
MimirVideoPixelFormat.Bgra8));
var deadline = Stopwatch.GetTimestamp() + (long)(timeoutMs / 1000.0 * Stopwatch.Frequency);
while (Stopwatch.GetTimestamp() < deadline)
{
if (!source.TryRead(out var sample))
{
Thread.Sleep(1);
continue;
}
var frame = sample.VideoFrame;
var expectedBytes = width * height * 4;
Console.WriteLine(
$"ffmpeg-rawvideo-source-smoke source={sample.SourceId} {frame?.Width}x{frame?.Height} format={frame?.PixelFormat} liveBytes={sample.ByteLength} payloadBytes={sample.Data.Length} stride={frame?.StrideBytes}");
return frame != null &&
frame.Width == width &&
frame.Height == height &&
frame.PixelFormat == MimirVideoPixelFormat.Bgra8 &&
frame.StrideBytes == width * 4 &&
sample.ByteLength == expectedBytes &&
sample.Data.Length == expectedBytes
? 0
: 1;
}
Console.WriteLine($"ffmpeg-rawvideo-source-smoke timeout source={sourceId} {width}x{height}@{fps}");
return 1;
}
static long StopwatchTicksToNs(long ticks)
{
return checked((long)(ticks * (1_000_000_000.0 / Stopwatch.Frequency)));

View file

@ -0,0 +1,183 @@
using System.Collections.Concurrent;
using System.Diagnostics;
namespace Mimir.Runtime.Synchronization;
public sealed record MimirFfmpegRawVideoStreamSourceOptions(
string Command,
IReadOnlyList<string> Arguments,
int Width,
int Height,
MimirVideoPixelFormat PixelFormat,
int FrameBytes = 0,
int StrideBytes = 0);
public sealed class MimirFfmpegRawVideoStreamSource : IMimirStreamSource
{
private readonly ConcurrentQueue<MimirStreamSample> samples = new();
private readonly CancellationTokenSource cancellation = new();
private readonly Process process;
private readonly Task stdoutTask;
private readonly Task stderrTask;
private readonly int frameBytes;
private readonly int strideBytes;
private ulong sequence;
private bool disposed;
public MimirFfmpegRawVideoStreamSource(
MimirStreamDescriptor descriptor,
MimirFfmpegRawVideoStreamSourceOptions options)
{
if (descriptor.Kind != MimirStreamKind.Video)
{
throw new ArgumentException("Raw FFmpeg video sources require a video descriptor.", nameof(descriptor));
}
if (options.Width <= 0 || options.Height <= 0)
{
throw new ArgumentOutOfRangeException(nameof(options), "Raw FFmpeg video sources require positive width and height.");
}
Descriptor = descriptor;
Options = options;
strideBytes = options.StrideBytes > 0 ? options.StrideBytes : InferStride(options.Width, options.PixelFormat);
frameBytes = options.FrameBytes > 0 ? options.FrameBytes : InferFrameBytes(options.Width, options.Height, options.PixelFormat, strideBytes);
if (strideBytes <= 0 || frameBytes <= 0)
{
throw new ArgumentOutOfRangeException(nameof(options), "Could not infer raw video frame size.");
}
process = StartProcess(options);
stdoutTask = Task.Run(() => ReadStdout(cancellation.Token));
stderrTask = Task.Run(() => DrainStderr(cancellation.Token));
}
public MimirStreamDescriptor Descriptor { get; }
public MimirFfmpegRawVideoStreamSourceOptions Options { get; }
public bool TryRead(out MimirStreamSample sample)
{
return samples.TryDequeue(out sample);
}
private static Process StartProcess(MimirFfmpegRawVideoStreamSourceOptions options)
{
var startInfo = new ProcessStartInfo(options.Command)
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
};
foreach (var argument in options.Arguments)
{
startInfo.ArgumentList.Add(argument);
}
return Process.Start(startInfo)
?? throw new InvalidOperationException($"Could not start FFmpeg raw video source process: {options.Command}");
}
private async Task ReadStdout(CancellationToken token)
{
var stream = process.StandardOutput.BaseStream;
while (!token.IsCancellationRequested)
{
var buffer = new byte[frameBytes];
var offset = 0;
while (offset < buffer.Length)
{
var read = await stream.ReadAsync(buffer.AsMemory(offset, buffer.Length - offset), token).ConfigureAwait(false);
if (read <= 0)
{
return;
}
offset += read;
}
var timestampNs = StopwatchTicksToNs(Stopwatch.GetTimestamp());
var videoFrame = new MimirVideoFrameDescriptor(
Options.Width,
Options.Height,
Options.PixelFormat,
strideBytes,
timestampNs);
samples.Enqueue(new MimirStreamSample(
Descriptor.SourceId,
Descriptor.Kind,
Descriptor.Origin,
timestampNs,
timestampNs,
sequence++,
0,
buffer.Length,
buffer,
videoFrame));
}
}
private async Task DrainStderr(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
var line = await process.StandardError.ReadLineAsync(token).ConfigureAwait(false);
if (line == null)
{
break;
}
}
}
private static int InferStride(int width, MimirVideoPixelFormat pixelFormat)
{
return pixelFormat switch
{
MimirVideoPixelFormat.Bgra8 => checked(width * 4),
MimirVideoPixelFormat.Nv12 => width,
MimirVideoPixelFormat.R8 or MimirVideoPixelFormat.Gray8 or MimirVideoPixelFormat.Bayer8 => width,
MimirVideoPixelFormat.Rg8 or MimirVideoPixelFormat.Yuy2 or MimirVideoPixelFormat.LeapStereoIr => checked(width * 2),
_ => 0,
};
}
private static int InferFrameBytes(int width, int height, MimirVideoPixelFormat pixelFormat, int strideBytes)
{
return pixelFormat switch
{
MimirVideoPixelFormat.Nv12 => checked(width * height * 3 / 2),
_ => checked(strideBytes * height),
};
}
private static long StopwatchTicksToNs(long ticks)
{
return checked((long)(ticks * (1_000_000_000.0 / Stopwatch.Frequency)));
}
public void Dispose()
{
if (disposed)
{
return;
}
disposed = true;
cancellation.Cancel();
try
{
if (!process.HasExited)
{
process.Kill(entireProcessTree: true);
}
}
catch
{
}
process.Dispose();
cancellation.Dispose();
}
}

View file

@ -234,6 +234,26 @@ public sealed class MimirRuntimeConfiguration
: null)));
}
if (string.Equals(stream.Adapter, "ffmpeg-rawvideo", StringComparison.OrdinalIgnoreCase)
|| string.Equals(stream.Adapter, "rawvideo-process", StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrWhiteSpace(stream.Command))
{
return null;
}
return new MimirStreamSourceFactory(descriptor, () => new MimirFfmpegRawVideoStreamSource(
descriptor,
new MimirFfmpegRawVideoStreamSourceOptions(
ResolveCommand(stream.Command, configDirectory),
stream.Arguments,
stream.Width,
stream.Height,
ParsePixelFormat(stream.PixelFormat),
stream.FrameBytes,
stream.StrideBytes)));
}
if (!string.Equals(stream.Adapter, "process", StringComparison.OrdinalIgnoreCase))
{
return null;
@ -282,6 +302,26 @@ public sealed class MimirRuntimeConfiguration
? origin
: throw new InvalidOperationException($"Unknown Mimir stream origin: {value}");
}
private static MimirVideoPixelFormat ParsePixelFormat(string value)
{
return string.IsNullOrWhiteSpace(value)
? MimirVideoPixelFormat.Unknown
: value.Trim().ToUpperInvariant() switch
{
"BAYER" or "BAYER8" => MimirVideoPixelFormat.Bayer8,
"GRAY8" or "Y8" => MimirVideoPixelFormat.Gray8,
"R8" => MimirVideoPixelFormat.R8,
"RG8" => MimirVideoPixelFormat.Rg8,
"YUY2" => MimirVideoPixelFormat.Yuy2,
"MJPG" or "MJPEG" => MimirVideoPixelFormat.Mjpg,
"H264" => MimirVideoPixelFormat.H264,
"NV12" => MimirVideoPixelFormat.Nv12,
"BGRA8" or "BGRA" => MimirVideoPixelFormat.Bgra8,
"LEAP_STEREO_IR" or "LEAPSTEREOIR" => MimirVideoPixelFormat.LeapStereoIr,
_ => MimirVideoPixelFormat.Unknown,
};
}
}
public sealed record MimirStreamSourceFactory(
@ -394,6 +434,10 @@ public sealed class MimirStreamConfig
public string PixelFormat { get; set; } = "";
public int FrameBytes { get; set; }
public int StrideBytes { get; set; }
public string InputFormat { get; set; } = "";
public string OutputFormat { get; set; } = "Nv12";

View file

@ -240,3 +240,5 @@
{"ts": "2026-05-30T22:01:42+01:00", "type": "implementation", "status": "ok", "note": "Self-hosted Mimir live edge has a deployed origin proof. Mimir.Broadcast builds the Starfire FFmpeg/NVENC RTMP push to rtmp://127.0.0.1:11935/live/mimir through an SSH local forward; Yggdrasil nginx now has libnginx-mod-rtmp, localhost RTMP ingest, /srv/mimir-live/hls, and a live.mimir.gamecult.org HTTP vhost with CORS for mimir.gamecult.org. An eight-second synthetic NVENC push on 2026-05-30 generated mimir.m3u8 and four TS segments on Yggdrasil. Public DNS/TLS for live.mimir.gamecult.org remains the activation blocker."}
{"ts": "2026-05-30T22:04:09+01:00", "type": "verification", "status": "ok", "note": "Yggdrasil target-host fanout smoke ran during an active Starfire synthetic NVENC push through the SSH RTMP tunnel. With LIVE_URL pinned to localhost via curl --resolve, the benchmark fetched a live HLS segment from nginx 320 times with CLIENTS=16 and REQUESTS_PER_CLIENT=20 in 1256 ms. wrk is not installed yet, so p95 latency remains future instrumentation."}
{"ts": "2026-05-30T22:18:07+01:00", "type": "decision", "status": "ok", "note": "Viewer authority moved to root gamecult-site at https://gamecult.org/livestream. Mimir no longer owns a static live viewer page; Mimir.Broadcast remains the Starfire sender, and Yggdrasil serves HLS through the existing StreamPixels TLS route https://streampixels.gamecult.org/mimir/live/hls/mimir.m3u8. The deployed StreamPixels nginx route health check returned mimir-live-ok, and an active synthetic NVENC push generated playlist/segments served publicly through StreamPixels; curl fallback benchmark fetched 320 segment requests in 6756 ms over HTTPS from Yggdrasil."}
{"ts": "2026-05-30T22:45:01+01:00", "type": "decision", "status": "ok", "note": "Added Raven/Eve ingest setup: ffmpeg-rawvideo runtime source decodes SRT feeds into Mimir video samples; raven-display listens on 5200, eve-camera on 5201; Raven desktop sender script uses gdigrab plus NVENC. Eve receiver lane exists but the iPad camera producer remains an explicit AVFoundation/external-app decision."}
{"ts": "2026-05-30T22:46:17+01:00", "type": "verification", "status": "warn", "note": "Raven/Eve ingest host probe: Eve SSH is reachable and reports iPadOS Darwin 20.3.0, but /var/mobile/Projects/Eve only contains the receiver/control app sources and no ffmpeg/avcapture helper was found. Raven SSH with the recorded key still fails publickey authentication, so Raven screen capture is prepared as a desktop-run script until SSH authority is repaired."}

View file

@ -259,6 +259,14 @@ accepted_design:
shared `clockDomainId`; the Raven path uses audio evidence routed into
Scarlett to earn a `raven-sync` correction for Raven display frames.
Network arrival timestamps remain metadata only.
`MimirFfmpegRawVideoStreamSource` is the network-pixel adapter for the
Raven/Eve ingest setup: FFmpeg owns SRT receive/decode, while Mimir owns
exact raw-video frame reads, payload geometry, video descriptors, and
rolling-buffer samples. `config/mimir-runtime.raven-eve.example.json`
listens for `raven-display` on port 5200 and `eve-camera` on port 5201.
Raven can send desktop capture with `scripts/start-raven-screen-capture-sender.ps1`.
Eve's receiver lane exists, but the iPad-side camera producer still needs a
native AVFoundation sender or a chosen external camera streaming app.
`MimirPresentationControlState` owns program-control intent for Fensalir:
video feed visibility/solo/opacity/layer order, audio mute/solo/gain, and
global LUT preset selection. The `Mimir Program` panel filters video