From 940038696c6108d6d8f528e782243677638560c6 Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:28:12 +0200 Subject: [PATCH 01/17] feat(transitions): support host-configured WebM scene overlays Add native alpha/audio stingers for Take and same-lane scene switches, with explicit configuration, frame-boundary completion and a typed client. No default media or Prism UI is included. Agent-Role: eleven Agent-Thread: 01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7 Work-Unit: local-20261008-webm-transition Issue: local-20261008-webm-transition Evidence: evidence/local-20261008-webm-transition/eleven/ --- CMakeLists.txt | 1 + docs/contracts/configurable-transitions.md | 206 +++++++++++ .../20261008T132750Z-contract-tests.xml | 1 + .../eleven/20261008T132750Z-native-tests.xml | 24 ++ .../eleven/20261008T132750Z-validation.json | 19 + packages/pulsar-client/README.md | 8 + packages/pulsar-client/src/client.ts | 8 + packages/pulsar-client/src/index.ts | 1 + packages/pulsar-client/src/transitions.ts | 110 ++++++ packages/pulsar-client/src/types.ts | 1 + .../pulsar-client/tests/transitions.test.ts | 66 ++++ plugins/pulsar-frontend-stub/CMakeLists.txt | 2 + plugins/pulsar-frontend-stub/README.md | 11 + .../include/pulsar-media-transition.h | 47 +++ .../src/pulsar-frontend-stub.cpp | 342 +++++++++++++++++- .../src/pulsar-media-transition-config.cpp | 42 +++ .../src/pulsar-media-transition.cpp | 140 +++++++ .../src/requesthandler/RequestHandler.cpp | 10 +- .../scene_control/test_dual_lane_contract.py | 12 +- scripts/probe-configurable-transition.py | 335 +++++++++++++++++ tests/pulsar-media-transition/CMakeLists.txt | 14 + .../pulsar-media-transition/config-probe.cpp | 35 ++ 22 files changed, 1408 insertions(+), 27 deletions(-) create mode 100644 docs/contracts/configurable-transitions.md create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T132750Z-contract-tests.xml create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T132750Z-native-tests.xml create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T132750Z-validation.json create mode 100644 packages/pulsar-client/src/transitions.ts create mode 100644 packages/pulsar-client/tests/transitions.test.ts create mode 100644 plugins/pulsar-frontend-stub/include/pulsar-media-transition.h create mode 100644 plugins/pulsar-frontend-stub/src/pulsar-media-transition-config.cpp create mode 100644 plugins/pulsar-frontend-stub/src/pulsar-media-transition.cpp create mode 100644 scripts/probe-configurable-transition.py create mode 100644 tests/pulsar-media-transition/CMakeLists.txt create mode 100644 tests/pulsar-media-transition/config-probe.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f7af66c..e8645ab5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,6 +170,7 @@ if(WIN32) # GPU, or running Pulsar process. add_subdirectory(tests/dual-lane-config) add_subdirectory(tests/pulsar-transition-controller) + add_subdirectory(tests/pulsar-media-transition) # #249 -- admission, in-flight callback accounting, and CEF close # finalization share one linearization point. This native test forces the diff --git a/docs/contracts/configurable-transitions.md b/docs/contracts/configurable-transitions.md new file mode 100644 index 00000000..b1784bda --- /dev/null +++ b/docs/contracts/configurable-transitions.md @@ -0,0 +1,206 @@ +# Host-controlled WebM transitions + +Owner: `plugins/pulsar-frontend-stub`. The media resource lives in +`pulsar-media-transition.{h,cpp}`, with pure wire validation in +`pulsar-media-transition-config.cpp`; the frontend owns the lane mutex, atomic +frame-boundary routing, admission and lifecycle. The `pulsar-transitions` +obs-websocket vendor and `PulsarClient.transitions` expose the capability to +hosts such as Prism. This increment changes no Prism code and includes no +default media or artwork. + +This capability uses Pulsar's existing dual-lane mode +(`PULSAR_DUAL_LANE_ENABLED=1`). Its existing activation and rollback gates +remain authoritative: outside that mode, or after a rollback freeze, +`operational` is false and configuration/switch requests are refused. + +## Configuration and readiness + +Use `CallVendorRequest` with `vendorName: "pulsar-transitions"`. `GetState` +takes `{}` and returns: + +```json +{ + "runtime_instance_id": "", + "operational": true, + "configured": false, + "config": {}, + "ready": false, + "readiness_error": "NOT_CONFIGURED", + "busy": false, + "media_duration_ms": 0, + "transition_duration_ms": 0, + "role_map": {"on_air": "A", "preview": "B"}, + "last_switch": {} +} +``` + +`Configure` takes the current runtime identity and a complete configuration: + +```json +{ + "runtime_instance_id": "", + "config": { + "path": "D:/media/operator-selected.webm", + "cut_point_ms": 880, + "volume": 0.5, + "muted": false + } +} +``` + +The path must be an absolute local `.webm` file. URLs, UNC paths, embedded +NULs, unknown configuration fields, fractional/bool cut points and invalid +gain/mute values are rejected. `cut_point_ms` is an integer in `[1, 19999]`. +`volume` defaults to 1 and is bounded to `[0,1]`; `muted` defaults to false. +No download, asset library, persistence or default file selection is performed +by Pulsar. A host stores its settings and reconfigures after a runtime restart. + +Configuration checks the container header and creates a private software +decoder. Its first decoded frame is uploaded on the video thread without +starting playback or sound. Acceptance is **not decoder readiness**. Poll `GetState` until +`ready` is true before taking or switching. Readiness requires decoded video +dimensions and a measured duration, with the cut point inside that duration. +Unsupported/corrupt media stays unready. The total transition is capped at +20 seconds. OBS adds a 250 ms completion tail to the measured media duration; +`transition_duration_ms` reports that effective native duration. The API does +not stretch the animation to an unrelated fade duration. + +An author must choose a fully opaque cut point in the actual animation. +Readiness validates decoding/timing, not opacity coverage or artistic quality. +Transparent WebM frames are composited by the existing OBS stinger renderer. +Only the media child's gain/mute is changed; surrounding scene and stream +audio gains are not changed. Preview lane playback mutes the media child; +the next Program/Take playback restores the configured gain/mute. + +`Clear` takes `{"runtime_instance_id":""}` and releases the +private decoder when idle. `Configure` and `Clear` cannot run during a +transition. The legacy fade/cut selection remains the fallback when the host +configuration is cleared. An explicitly supplied legacy `PULSAR_STINGER_ASSET` +is still supported; the former automatic demo-path resolution is removed. +The host API works without either legacy native-transition environment flag. + +## Two distinct switch operations + +**Take:** the existing `pulsar-scene-switch` Prepare/PreviewReady/Take path uses +the configured stinger automatically. Its role swap, CAS guards, freeze and +terminal `TakeCommitted` event are retained. An unready configured media +resource rejects admission instead of silently performing a hard cut. +The visual cut occurs at `cut_point_ms`; `TakeCommitted` is still the terminal +frame boundary after the transition, not the visual midpoint. + +**Same physical lane:** stage an independently ready OBS scene first, then +call `SwitchLane`: + +```json +{ + "runtime_instance_id": "", + "command_id": "switch-unique-001", + "lane_id": "A", + "expected_scene_name": "currently selected scene", + "scene_name": "prepared incoming scene" +} +``` + +The selected physical lane animates from its current composition to the +incoming one. The other lane, role map, views, video surfaces and output +bindings remain unchanged. The final frame callback installs the new child +in the existing lane root and updates the selected scene and matching +scene-switch revision. It does not perform a Take or increment the role-map +revision. Without a configured stinger this operation is an atomic hard cut. + +The destination must be an existing OBS scene, distinct from the other lane's +selected scene and both physical lane roots. A same-scene request returns +`status: "noop"`. The caller stages the complete target graph and establishes +its source readiness before sending this request. Pulsar cannot infer a +scene replacement occurring inside a persistent browser/DOM source: a future +host integration must stage a distinct composition and invoke this API, not +mutate the visible page before asking for a transition. Plain scene selection +and Prepare remain preparation operations; they do not start this effect. + +One transition is admitted at a time. Scene/input/settings mutations are +frozen from acceptance until the terminal frame callback. An outstanding +v1 preparation must be completed or expire before `SwitchLane` is admitted. +`expected_scene_name` and `runtime_instance_id` reject stale host commands. +Command IDs use 1–128 ASCII alphanumeric/`._:-` characters. Accepted outcomes +are retained for the runtime lifetime up to 1024 commands; capacity exhaustion +fails closed. Replaying an identical ID/payload returns its current outcome; +using an ID with another payload returns `IDEMPOTENCY_CONFLICT`. A retry while +the mutation gate is closed is rejected as `PREVIEW_FROZEN`; use readback. + +## Completion, abort and errors + +`SwitchLane` initially returns `status: "accepted"` and its runtime, command, +lane and scene identities. Subscribe to vendor event `LaneSwitchCompleted`, +or call `GetResult` with the runtime and command IDs. The terminal result has +`status: "completed" | "aborted" | "failed"`, `frame_id`, `pts_ns`, and the +unchanged `role_map`. Failed replacement carries `failure_reason`. Noop is +already terminal and does not fabricate frame evidence or emit a completion +event. `GetState.last_switch` is a convenience; `GetResult` is the correlated +source when another client may have submitted a later operation. + +`Abort` takes the runtime and command IDs, responds `status: "aborting"`, and +restores the outgoing composition at a native frame boundary. Await the +terminal result. It cannot cancel another command or a v1 Take. Use the +existing scene-switch Abort for a Take. GetState/GetResult/Abort are the only +new operations allowed through the pending-mutation gate. + +Vendor validation failures return `{ "error": "CODE" }`; transport admission +failures use the existing obs-websocket error envelope. Stable codes include +`REQUEST_INVALID`, `RUNTIME_MISMATCH`, `RUNTIME_UNAVAILABLE`, `CONFIG_INVALID`, +`LOCAL_PATH_REQUIRED`, `WEBM_REQUIRED`, `ASSET_MISSING`, +`ASSET_INVALID_CONTAINER`, `DECODER_UNAVAILABLE`, `MEDIA_NOT_READY`, +`DECODE_FAILED`, `DURATION_INVALID`, `CUT_POINT_INVALID`, +`CUT_POINT_OUTSIDE_MEDIA`, `VOLUME_INVALID`, `MUTED_INVALID`, +`TRANSITION_BUSY`, `SCENE_NOT_FOUND`, `SCENE_MISMATCH`, `LANE_ALIAS`, +`IDEMPOTENCY_CONFLICT`, `COMMAND_CAPACITY_REACHED`, `COMMAND_NOT_FOUND`, +`COMMAND_NOT_PENDING`, `TRANSITION_START_FAILED`, and `ATOMIC_SWAP_REJECTED`. + +The libobs data transport does not preserve JSON nulls. Native empty state +uses empty objects/strings; the TypeScript client normalizes absent config, +last switch and readiness error to null. `client.transitions.configure(id, +null)` maps to the explicit Clear request. + +## Host client example + +```ts +const state = await client.transitions.getState(); +await client.transitions.configure(state.runtime_instance_id, { + path: selectedLocalWebM, + cutPointMs: selectedOpaquePoint, + volume: 0.5, +}); +// Poll getState until ready, and stage/validate the incoming OBS scene. +client.on("laneSwitchCompleted", (result) => { + // Match runtime_instance_id AND command_id before updating host UI. +}); +await client.transitions.switchLane({ + runtimeInstanceId: state.runtime_instance_id, + commandId: crypto.randomUUID(), + lane: state.role_map.on_air, + expectedSceneName: outgoingSceneName, + sceneName: incomingSceneName, +}); +``` + +## Validation and ownership + +- `tests/pulsar-media-transition`: production parser cases, no capture/stream. +- `tests/pulsar-transition-controller`: existing native lifecycle regression. +- `packages/pulsar-client/tests/transitions.test.ts`: typed request mapping, + explicit Clear, error propagation and completion events over the OBS client. +- `scripts/probe-configurable-transition.py`: isolated native runtime, + generated synthetic alpha WebM, same-lane Program and Preview, Program + recording pixels/audio and partial-alpha blending, Take after Preview, + busy/stale/alias guards, replacement by a second file, media gain/mute, + replay after abort and a hard cut after clear. Unready/invalid media cannot + silently bypass the effect. Generated files are test artifacts, not product assets. + +Run the opt-in probe with a dedicated build and explicit output directory: + +```powershell +python scripts/probe-configurable-transition.py --exe --output +``` + +The probe never connects to an existing user runtime, never starts a stream, +and mutes capture inputs before recording its synthetic scenes. Publication, +runtime activation and any future Prism UI integration are separate work. diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T132750Z-contract-tests.xml b/evidence/local-20261008-webm-transition/eleven/20261008T132750Z-contract-tests.xml new file mode 100644 index 00000000..25816e88 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T132750Z-contract-tests.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T132750Z-native-tests.xml b/evidence/local-20261008-webm-transition/eleven/20261008T132750Z-native-tests.xml new file mode 100644 index 00000000..3fa4c3bb --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T132750Z-native-tests.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + media configuration: valid custom WebM accepted; 22 invalid cases rejected + + + diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T132750Z-validation.json b/evidence/local-20261008-webm-transition/eleven/20261008T132750Z-validation.json new file mode 100644 index 00000000..a74b0d57 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T132750Z-validation.json @@ -0,0 +1,19 @@ +{ + "work_unit": "local-20261008-webm-transition", + "agent_role": "eleven", + "agent_thread": "01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7", + "base_revision": "ad2c290bb7ab1f73edc68add30fc31d35a577adc", + "recorded_at": "2026-10-08T13:27:50.7096735Z", + "authority": "User requested Pulsar-only WebM transition capability, no ADR, no default asset, then branch push and local Prism test. No merge or release.", + "scope": "Native host API, typed client, contracts and isolated probe. Prism product code unchanged.", + "prior_local_validation": { + "native_build": "pulsar-headless, pulsar-websocket and both transition probes built successfully", + "native_ctest": "2 passed", + "typescript": "build/typecheck and 74 tests passed", + "contracts": "173 passed", + "isolated_runtime_probe": "Passed in prior implementation turn: alpha/audio, Program/Preview, TakeCommitted, replacement, abort/replay, volume/mute, Clear, guards and graceful shutdown. Runtime capture artifacts no longer present; no persisted capture claimed here." + }, + "current_validation": "git diff --check passed", + "pending": "Real Prism test using final user asset", + "binary_sha256": "9C17B896EB365D76510BE2DAC9E5682AC62FAF3616F6A5D3BB4E40B42B1BA5CD" +} diff --git a/packages/pulsar-client/README.md b/packages/pulsar-client/README.md index 5b8924f5..053281d2 100644 --- a/packages/pulsar-client/README.md +++ b/packages/pulsar-client/README.md @@ -1,5 +1,13 @@ # @clodocapeo/pulsar-client +`client.transitions` exposes the runtime-local configurable WebM capability: +`getState()`, `configure(runtimeId, config | null)`, `switchLane(request)`, +`getResult(runtimeId, commandId)` and `abort(runtimeId, commandId)`. +Observe `laneSwitchCompleted` for terminal frame/PTS evidence. Configuration +loads no default file and host persistence remains external. See the +[native contract and host example](../../docs/contracts/configurable-transitions.md) +for readiness, Take versus same-lane semantics, media-only gain and errors. + [![npm](https://img.shields.io/npm/v/%40clodocapeo%2Fpulsar-client?logo=npm&color=cb3837)](https://www.npmjs.com/package/@clodocapeo/pulsar-client) [![Licence MIT](https://img.shields.io/badge/licence-MIT-blue)](./LICENSE) [![Node ≥ 18](https://img.shields.io/badge/node-%E2%89%A518-339933)](https://nodejs.org) diff --git a/packages/pulsar-client/src/client.ts b/packages/pulsar-client/src/client.ts index 51f52bcc..f36c49c1 100644 --- a/packages/pulsar-client/src/client.ts +++ b/packages/pulsar-client/src/client.ts @@ -9,6 +9,8 @@ import { AdaptiveNamespace } from "./adaptive.js"; import { RecordNamespace } from "./record.js"; import { StreamNamespace } from "./stream.js"; import { AudioNamespace } from "./audio.js"; +import { TransitionsNamespace } from "./transitions.js"; +import type { LaneSwitchResult } from "./transitions.js"; import { bitrateAdjustedFromWire, type WireBitrateAdjustedEvent } from "./wire.js"; import type { ConnectOptions, @@ -60,6 +62,7 @@ export class PulsarClient extends TypedEventEmitter { public readonly record: RecordNamespace; public readonly stream: StreamNamespace; public readonly audio: AudioNamespace; + public readonly transitions: TransitionsNamespace; private connected = false; @@ -73,6 +76,7 @@ export class PulsarClient extends TypedEventEmitter { this.record = new RecordNamespace(this); this.stream = new StreamNamespace(this); this.audio = new AudioNamespace(this); + this.transitions = new TransitionsNamespace(this); this.obs.on("ConnectionClosed", (info) => { this.connected = false; @@ -109,6 +113,10 @@ export class PulsarClient extends TypedEventEmitter { // Pulsar vendor events -- VendorEvent payload includes vendorName + // eventType + eventData. We dispatch on eventType under our namespace. this.obs.on("VendorEvent", (data) => { + if (data.vendorName === "pulsar-transitions" && data.eventType === "LaneSwitchCompleted") { + this.emit("laneSwitchCompleted", data.eventData as unknown as LaneSwitchResult); + return; + } if (data.vendorName !== VENDOR) return; switch (data.eventType) { case "BitrateAdjusted": { diff --git a/packages/pulsar-client/src/index.ts b/packages/pulsar-client/src/index.ts index 397fe397..6705bf7a 100644 --- a/packages/pulsar-client/src/index.ts +++ b/packages/pulsar-client/src/index.ts @@ -4,6 +4,7 @@ // See README.md for a full example. export { PulsarClient } from "./client.js"; +export type { StingerConfig, TransitionState, TransitionLane, SwitchLaneRequest, LaneSwitchResult } from "./transitions.js"; export { PulsarVendorError, PulsarNotConnectedError, diff --git a/packages/pulsar-client/src/transitions.ts b/packages/pulsar-client/src/transitions.ts new file mode 100644 index 00000000..629c1fce --- /dev/null +++ b/packages/pulsar-client/src/transitions.ts @@ -0,0 +1,110 @@ +import type { PulsarClient } from "./client.js"; +import { PulsarVendorError } from "./errors.js"; + +export type TransitionLane = "A" | "B"; + +export interface StingerConfig { + /** Absolute local WebM path on the machine running Pulsar. No URL/download. */ + path: string; + /** Author-chosen fully opaque point in the animation, in milliseconds. */ + cutPointMs: number; + /** Media sound only; 0..1. Scene, microphone and music gains are unchanged. */ + volume?: number; + muted?: boolean; +} + +export interface LaneSwitchResult { + runtime_instance_id: string; + command_id: string; + lane_id: TransitionLane; + scene_name: string; + status: "accepted" | "completed" | "aborted" | "failed" | "noop"; + frame_id?: number; + pts_ns?: number; + failure_reason?: string; + role_map?: { on_air: TransitionLane; preview: TransitionLane }; +} + +export interface TransitionState { + runtime_instance_id: string; + operational: boolean; + busy: boolean; + configured: boolean; + config: { path: string; cut_point_ms: number; volume: number; muted: boolean } | null; + ready: boolean; + readiness_error: string | null; + media_duration_ms: number; + /** Measured media duration plus OBS's fixed 250 ms completion tail. */ + transition_duration_ms: number; + role_map: { on_air: TransitionLane; preview: TransitionLane }; + last_switch: LaneSwitchResult | null; +} + +export interface SwitchLaneRequest { + runtimeInstanceId: string; + commandId: string; + lane: TransitionLane; + sceneName: string; + expectedSceneName: string; +} + +/** Runtime-local transition capability. Persistence and media selection belong + * to the host. Configure once, wait for ready, then use Prepare/Take or switchLane. + * Acceptance is not completion: observe laneSwitchCompleted or getResult. + */ +export class TransitionsNamespace { + constructor(private readonly client: PulsarClient) {} + + getState(): Promise { + return this.request("GetState", {}).then(normalizeState); + } + + configure(runtimeInstanceId: string, config: StingerConfig | null): Promise { + if (config === null) return this.request("Clear", { runtime_instance_id: runtimeInstanceId }).then(normalizeState); + return this.request("Configure", { + runtime_instance_id: runtimeInstanceId, + config: { + path: config.path, + cut_point_ms: config.cutPointMs, + volume: config.volume ?? 1, + muted: config.muted ?? false, + }, + }).then(normalizeState); + } + + switchLane(request: SwitchLaneRequest): Promise { + return this.request("SwitchLane", { + runtime_instance_id: request.runtimeInstanceId, + command_id: request.commandId, + lane_id: request.lane, + scene_name: request.sceneName, + expected_scene_name: request.expectedSceneName, + }); + } + + getResult(runtimeInstanceId: string, commandId: string): Promise { + return this.request("GetResult", { runtime_instance_id: runtimeInstanceId, command_id: commandId }); + } + + abort(runtimeInstanceId: string, commandId: string): Promise<{ command_id: string; status: "aborting" }> { + return this.request("Abort", { runtime_instance_id: runtimeInstanceId, command_id: commandId }); + } + + private async request(requestType: string, requestData: Record): Promise { + const response = await this.client.call<{ responseData?: T & { error?: string } }>("CallVendorRequest", { + vendorName: "pulsar-transitions", requestType, requestData, + }); + const data = response.responseData; + if (!data || typeof data !== "object") throw new PulsarVendorError(requestType, "RESPONSE_INVALID"); + if (data.error) throw new PulsarVendorError(requestType, data.error); + return data; + } +} + +// obs_data's transport has no JSON null: empty objects/strings express the +// absent state on the wire. Present explicit nulls to host UI consumers. +function normalizeState(state: TransitionState): TransitionState { + return { ...state, config: state.config?.path ? state.config : null, + readiness_error: state.readiness_error || null, + last_switch: state.last_switch?.command_id ? state.last_switch : null }; +} diff --git a/packages/pulsar-client/src/types.ts b/packages/pulsar-client/src/types.ts index 14175368..38ab6e6e 100644 --- a/packages/pulsar-client/src/types.ts +++ b/packages/pulsar-client/src/types.ts @@ -487,6 +487,7 @@ export interface PulsarPrismLogEvent { /** Mapping of typed event names to their payloads. */ export interface PulsarEventMap { + laneSwitchCompleted: import("./transitions.js").LaneSwitchResult; prismLog: PulsarPrismLogEvent; bitrateAdjusted: BitrateAdjustedEvent; recordStateChanged: RecordStateChangedEvent; diff --git a/packages/pulsar-client/tests/transitions.test.ts b/packages/pulsar-client/tests/transitions.test.ts new file mode 100644 index 00000000..e6550b9c --- /dev/null +++ b/packages/pulsar-client/tests/transitions.test.ts @@ -0,0 +1,66 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { PulsarClient, PulsarVendorError } from "../src/index.js"; +import { MockObsWebSocket } from "./mock-server.js"; + +describe("host-controlled transitions", () => { + let server: MockObsWebSocket; + let client: PulsarClient; + beforeEach(async () => { + server = await MockObsWebSocket.create(); + client = new PulsarClient(); + await client.connect({ url: server.url }); + }); + afterEach(async () => { await client.disconnect(); await server.close(); vi.restoreAllMocks(); }); + + it("reads empty state without configuring or loading a default asset", async () => { + const call = vi.spyOn(client.obs, "call").mockResolvedValue({ responseData: { configured: false, config: {}, last_switch: {}, readiness_error: "", ready: false } } as never); + expect(await client.transitions.getState()).toMatchObject({ configured: false, config: null, last_switch: null, readiness_error: null, ready: false }); + expect(call).toHaveBeenCalledTimes(1); + expect(call).toHaveBeenCalledWith("CallVendorRequest", { + vendorName: "pulsar-transitions", requestType: "GetState", requestData: {}, + }); + }); + + it("carries the runtime guard, custom file, cut point and media gain", async () => { + const call = vi.spyOn(client.obs, "call").mockResolvedValue({ responseData: { ready: false } } as never); + await client.transitions.configure("runtime-1", { path: "D:/effects/custom.webm", cutPointMs: 880, volume: 0.4, muted: true }); + expect(call).toHaveBeenLastCalledWith("CallVendorRequest", { + vendorName: "pulsar-transitions", requestType: "Configure", + requestData: { runtime_instance_id: "runtime-1", config: { path: "D:/effects/custom.webm", cut_point_ms: 880, volume: 0.4, muted: true } }, + }); + await client.transitions.configure("runtime-1", null); + expect(call).toHaveBeenLastCalledWith("CallVendorRequest", expect.objectContaining({ requestType: "Clear", requestData: { runtime_instance_id: "runtime-1" } })); + }); + + it("does not turn same-lane acceptance into a Take or completion", async () => { + const call = vi.spyOn(client.obs, "call").mockResolvedValue({ responseData: { command_id: "switch-1", status: "accepted" } } as never); + const result = await client.transitions.switchLane({ runtimeInstanceId: "runtime-1", commandId: "switch-1", lane: "A", sceneName: "next", expectedSceneName: "old" }); + expect(result.status).toBe("accepted"); + expect(call).toHaveBeenCalledTimes(1); + expect(call).toHaveBeenCalledWith("CallVendorRequest", { + vendorName: "pulsar-transitions", requestType: "SwitchLane", + requestData: { runtime_instance_id: "runtime-1", command_id: "switch-1", lane_id: "A", scene_name: "next", expected_scene_name: "old" }, + }); + }); + + it("reads a correlated terminal result and aborts only a named command", async () => { + const call = vi.spyOn(client.obs, "call").mockResolvedValue({ responseData: { status: "aborted", command_id: "switch-1" } } as never); + expect((await client.transitions.getResult("runtime-1", "switch-1")).status).toBe("aborted"); + expect(call).toHaveBeenLastCalledWith("CallVendorRequest", expect.objectContaining({ requestType: "GetResult", requestData: { runtime_instance_id: "runtime-1", command_id: "switch-1" } })); + await client.transitions.abort("runtime-1", "switch-1"); + expect(call).toHaveBeenLastCalledWith("CallVendorRequest", expect.objectContaining({ requestType: "Abort", requestData: { runtime_instance_id: "runtime-1", command_id: "switch-1" } })); + }); + + it.each(["MEDIA_NOT_READY", "CUT_POINT_OUTSIDE_MEDIA", "SCENE_MISMATCH", "RUNTIME_MISMATCH", "TRANSITION_BUSY"])("surfaces %s without falling back to a hard cut", async (error) => { + const call = vi.spyOn(client.obs, "call").mockResolvedValue({ responseData: { error } } as never); + await expect(client.transitions.getState()).rejects.toBeInstanceOf(PulsarVendorError); + expect(call).toHaveBeenCalledTimes(1); + }); + + it("routes the native completion event without consuming another vendor's event", async () => { + const event = { runtime_instance_id: "runtime-1", command_id: "switch-1", lane_id: "A", scene_name: "next", status: "completed", frame_id: 70, pts_ns: 2000 }; + const received = new Promise((resolve) => client.on("laneSwitchCompleted", resolve)); + server.emitEvent("VendorEvent", { vendorName: "pulsar-transitions", eventType: "LaneSwitchCompleted", eventData: event }); + expect(await received).toEqual(event); + }); +}); diff --git a/plugins/pulsar-frontend-stub/CMakeLists.txt b/plugins/pulsar-frontend-stub/CMakeLists.txt index e3a316c1..f7eb3ba6 100644 --- a/plugins/pulsar-frontend-stub/CMakeLists.txt +++ b/plugins/pulsar-frontend-stub/CMakeLists.txt @@ -70,6 +70,8 @@ endif() add_library(pulsar-frontend-stub STATIC src/pulsar-frontend-stub.cpp + src/pulsar-media-transition.cpp + src/pulsar-media-transition-config.cpp ) target_include_directories(pulsar-frontend-stub PUBLIC diff --git a/plugins/pulsar-frontend-stub/README.md b/plugins/pulsar-frontend-stub/README.md index af8a91aa..23d77219 100644 --- a/plugins/pulsar-frontend-stub/README.md +++ b/plugins/pulsar-frontend-stub/README.md @@ -2,6 +2,17 @@ Frontend callbacks for Pulsar's headless service. +The host-controlled WebM capability is documented in +[configurable-transitions](../../docs/contracts/configurable-transitions.md). +`src/pulsar-media-transition.cpp` owns the private decoder and media-only +audio settings; `src/pulsar-media-transition-config.cpp` validates the host +configuration; `pulsar-frontend-stub.cpp` owns its vendor API, +lane admission and frame-boundary completion. It starts without a media +resource, can be configured at runtime, and supports Take and same-lane +replacement without changing the role map for the latter. The historical +environment-only demo stinger described below remains an explicit opt-in; +there is no automatically selected demo path. + `libobs` exposes a `frontend-api` layer (`obs-frontend-api.dll`) whose function table is filled by whichever frontend is running — the OBS Studio Qt UI in upstream, this stub in Pulsar. Without callbacks set, diff --git a/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h b/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h new file mode 100644 index 00000000..66af0b89 --- /dev/null +++ b/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace pulsar_transition { + +struct MediaConfig { + std::string path; + uint32_t cut_point_ms = 0; + float volume = 1.0f; + bool muted = false; +}; + +// Pure wire validation; file/decoder checks +// belong to MediaTransition. No filename or artwork is built into the feature. +bool parse_media_config(const nlohmann::json &value, MediaConfig &out, std::string &error); + +// Called only under the frontend lane mutex. Owns a private OBS stinger and +// retains its private media child. Never scales the surrounding scene audio. +class MediaTransition { +public: + ~MediaTransition(); + MediaTransition() = default; + MediaTransition(const MediaTransition &) = delete; + MediaTransition &operator=(const MediaTransition &) = delete; + + bool configure(const nlohmann::json &value, std::string &error); + void clear(); + bool configured() const { return source_ != nullptr; } + nlohmann::json state() const; + void preload_tick(); // video thread only; never starts playback or audio + obs_source_t *prepare(bool preview, uint32_t &duration_ms, std::string &error); + void finish(); + +private: + obs_source_t *media() const; + uint64_t media_duration_ns() const; + std::string readiness_error() const; + MediaConfig config_; + obs_source_t *source_ = nullptr; + mutable obs_source_t *media_ = nullptr; // retained after the deferred source update + bool prepared_ = false; +}; + +} // namespace pulsar_transition diff --git a/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp b/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp index 08ef3fe3..eb622b3e 100644 --- a/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp +++ b/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp @@ -105,6 +105,7 @@ #include "pulsar-frontend-stub.h" #include "pulsar-dual-lane-config.h" #include "pulsar-transition-controller.h" +#include "pulsar-media-transition.h" #include "pulsar-dual-lane-control.h" #include "pulsar-runtime-telemetry.h" #include "pulsar-runtime-telemetry-signals.h" @@ -2902,6 +2903,12 @@ class PulsarFrontendAPI : public obs_frontend_callbacks { bool sceneSwitchAbort(const std::string &takeCommandId); void sceneSwitchClearPrepared(const std::string &commandId); void dualLaneTransitionTick(); + json mediaTransitionState(); + json configureMediaTransition(const json &config); + json switchLaneScene(const json &request); + json abortLaneSwitch(const std::string &commandId); + json laneSwitchResult(const std::string &commandId); + static void OnLaneSwitchCommitted(void *param, uint64_t frameId, uint64_t ptsNs); static void OnSceneSwitchPreviewVideoFrame(void *param, struct video_data *frame); static void OnDualLaneTick(void *param, float seconds); static void OnDualLaneTransitionStarted(void *param, uint64_t frameId, uint64_t ptsNs); @@ -3592,14 +3599,9 @@ class PulsarFrontendAPI : public obs_frontend_callbacks { { if (const char *e = std::getenv("PULSAR_STINGER_ASSET"); e && *e) return e; - // Default: /../../data/pulsar/stinger-demo.webm. pulsar.exe runs - // with cwd=bin/64bit (PRISM-EMBEDDING.md), so ../../data is the bundle - // data root. Absent asset => the stinger simply decodes nothing; the - // fade fallback still composites and the encoder is never blanked. - std::error_code ec; - std::filesystem::path p = - std::filesystem::current_path(ec) / ".." / ".." / "data" / "pulsar" / "stinger-demo.webm"; - return std::filesystem::weakly_canonical(p, ec).string(); + // No default media. The legacy env override is an explicit operator + // choice; the host-controlled API starts with an empty configuration. + return {}; } // obs_transition_start can report success before the media decoder has @@ -3862,6 +3864,14 @@ class PulsarFrontendAPI : public obs_frontend_callbacks { bool dualLaneTransitionFinalPending = false; bool dualLaneTransitionAbortPending = false; uint64_t dualLaneTransitionStartNs = 0; + pulsar_transition::MediaTransition mediaTransition; + int sameLaneTarget = -1; + obs_source_t *sameLaneScene = nullptr; + bool sameLaneAborted = false; + std::string sameLaneCommandId; + struct LaneOutcome { json request; json result; }; + std::map laneOutcomes; + json lastLaneSwitch = nullptr; std::atomic recordingPaused{false}; std::string lastRecording; std::string lastReplay; @@ -3962,6 +3972,17 @@ class PulsarSceneSwitchVendor { pendingTake_.reset(); } + // An in-lane replacement changes the selected scene, never the role map. + // Invalidate an older prepared command so its CAS guard cannot name the + // scene which was visible before this independently committed operation. + void laneSceneChanged(bool program) + { + std::lock_guard lock(mutex_); + advanceRevision(program ? "program" : "preview"); + pendingPrepare_.reset(); + state_ = operational_ ? "ready" : "frozen"; + } + void takeCommitted(const std::string &takeId, uint64_t frameId, uint64_t ptsNs, bool freezeAfterCommit) { @@ -4285,6 +4306,240 @@ class PulsarSceneSwitchVendor { PulsarSceneSwitchVendor g_sceneSwitchVendorStorage; +// Host API, separate from the frozen Prepare/Take v1 envelope. No private +// media path is accepted through a scene leaf. Only an explicit host request +// configures this runtime-local resource. +class PulsarMediaTransitionVendor { +public: + bool start() + { + std::lock_guard lock(mutex_); + vendor_ = obs_websocket_register_vendor("pulsar-transitions"); + if (!vendor_) return false; + running_ = obs_websocket_vendor_register_request(vendor_, "Configure", &Configure, this) && + obs_websocket_vendor_register_request(vendor_, "Clear", &Clear, this) && + obs_websocket_vendor_register_request(vendor_, "GetState", &GetState, this) && + obs_websocket_vendor_register_request(vendor_, "SwitchLane", &SwitchLane, this) && + obs_websocket_vendor_register_request(vendor_, "GetResult", &GetResult, this) && + obs_websocket_vendor_register_request(vendor_, "Abort", &Abort, this); + return running_; + } + void stop() { std::lock_guard lock(mutex_); running_ = false; } + void emit(const json &event) + { + // No vendor lock from a graphics callback: a request may be holding + // it while waiting for that exact frame boundary to drain. + if (!vendor_) return; + OBSDataAutoRelease data = obs_data_create_from_json(event.dump().c_str()); + if (data) obs_websocket_vendor_emit_event(vendor_, "LaneSwitchCompleted", data); + } +private: + static void Configure(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("Configure", a, b); } + static void Clear(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("Clear", a, b); } + static void GetState(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("GetState", a, b); } + static void SwitchLane(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("SwitchLane", a, b); } + static void GetResult(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("GetResult", a, b); } + static void Abort(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("Abort", a, b); } + void dispatch(const std::string &operation, obs_data_t *request, obs_data_t *response) + { + std::lock_guard lock(mutex_); + json out; + try { + const char *raw = request ? obs_data_get_json(request) : nullptr; + const json value = raw ? json::parse(raw) : json::object(); + if (!running_ || !g_api) out = {{"error", "RUNTIME_UNAVAILABLE"}}; + else if (!value.is_object()) out = {{"error", "REQUEST_INVALID"}}; + else if (operation == "GetState") out = g_api->mediaTransitionState(); + else { + const char *id = std::getenv("PULSAR_RUNTIME_INSTANCE_ID"); + const std::string runtime = id && *id ? id : "pulsar-runtime"; + if (!value.contains("runtime_instance_id") || !value["runtime_instance_id"].is_string() || + value["runtime_instance_id"] != runtime) out = {{"error", "RUNTIME_MISMATCH"}}; + else if (operation == "Configure" && value.contains("config") && value.size() == 2) + out = g_api->configureMediaTransition(value["config"]); + else if (operation == "Clear" && value.size() == 1) out = g_api->configureMediaTransition(nullptr); + else if (operation == "SwitchLane") out = g_api->switchLaneScene(value); + else if ((operation == "Abort" || operation == "GetResult") && value.size() == 2 && + value.contains("command_id") && value["command_id"].is_string()) + out = operation == "Abort" ? g_api->abortLaneSwitch(value["command_id"]) : + g_api->laneSwitchResult(value["command_id"]); + else out = {{"error", "REQUEST_INVALID"}}; + } + } catch (const std::exception &) { out = {{"error", "REQUEST_INVALID"}}; } + OBSDataAutoRelease data = obs_data_create_from_json(out.dump().c_str()); + if (data && response) obs_data_apply(response, data); + } + std::mutex mutex_; + bool running_ = false; + obs_websocket_vendor vendor_ = nullptr; +}; +PulsarMediaTransitionVendor g_mediaTransitionVendor; + +json PulsarFrontendAPI::mediaTransitionState() +{ + std::lock_guard lock(dualLaneMutex); + auto state = mediaTransition.state(); + const char *id = std::getenv("PULSAR_RUNTIME_INSTANCE_ID"); + state["runtime_instance_id"] = id && *id ? id : "pulsar-runtime"; + state["operational"] = dualLaneReady && dualLaneOperational; + state["busy"] = dualLaneCutPending.load(); + state["role_map"] = {{"on_air", onAirLane == 0 ? "A" : "B"}, {"preview", previewLane == 0 ? "A" : "B"}}; + state["last_switch"] = lastLaneSwitch.is_null() ? json::object() : lastLaneSwitch; + return state; +} + +json PulsarFrontendAPI::configureMediaTransition(const json &config) +{ + { + std::lock_guard lock(dualLaneMutex); + if (!dualLaneReady || !dualLaneOperational) return {{"error", "RUNTIME_UNAVAILABLE"}}; + if (dualLaneCutPending.load()) return {{"error", "TRANSITION_BUSY"}}; + std::string error; + if (!mediaTransition.configure(config, error)) return {{"error", error}}; + } + return mediaTransitionState(); +} + +json PulsarFrontendAPI::laneSwitchResult(const std::string &commandId) +{ + std::lock_guard lock(dualLaneMutex); + const auto it = laneOutcomes.find(commandId); + return it == laneOutcomes.end() ? json{{"error", "COMMAND_NOT_FOUND"}} : it->second.result; +} + +json PulsarFrontendAPI::switchLaneScene(const json &request) +{ + if (request.size() != 5 || !request.contains("command_id") || !request["command_id"].is_string() || + !request.contains("lane_id") || !request["lane_id"].is_string() || + !request.contains("scene_name") || !request["scene_name"].is_string() || + !request.contains("expected_scene_name") || !request["expected_scene_name"].is_string()) + return {{"error", "REQUEST_INVALID"}}; + const auto command = request["command_id"].get(); + const auto name = request["scene_name"].get(); + const auto expected = request["expected_scene_name"].get(); + const auto laneName = request["lane_id"].get(); + if (command.empty() || command.size() > 128 || name.empty() || name.size() > 1024 || + expected.empty() || expected.size() > 1024 || command.find_first_not_of( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._:-") != std::string::npos || + name.find('\0') != std::string::npos || expected.find('\0') != std::string::npos || + (laneName != "A" && laneName != "B")) return {{"error", "REQUEST_INVALID"}}; + OBSSourceAutoRelease incoming = obs_get_source_by_name(name.c_str()); + std::lock_guard lock(dualLaneMutex); + const auto existing = laneOutcomes.find(command); + if (existing != laneOutcomes.end()) return existing->second.request == request ? existing->second.result : + json{{"error", "IDEMPOTENCY_CONFLICT"}}; + if (!dualLaneReady || !dualLaneOperational) return {{"error", "RUNTIME_UNAVAILABLE"}}; + if (dualLaneCutPending.load() || !sceneSwitchPreparedCommandId.empty()) return {{"error", "TRANSITION_BUSY"}}; + if (laneOutcomes.size() >= 1024) return {{"error", "COMMAND_CAPACITY_REACHED"}}; + const int lane = laneName == "A" ? 0 : 1; + obs_source_t *outgoing = lane == onAirLane ? programSelection : previewSelection; + obs_source_t *other = lane == onAirLane ? previewSelection : programSelection; + if (!incoming || !obs_scene_from_source(incoming)) return {{"error", "SCENE_NOT_FOUND"}}; + if (!outgoing || expected != obs_source_get_name(outgoing)) return {{"error", "SCENE_MISMATCH"}}; + if (incoming == other || incoming == laneSources[0] || incoming == laneSources[1]) + return {{"error", "LANE_ALIAS"}}; + json result = {{"command_id", command}, {"lane_id", laneName}, {"scene_name", name}, + {"runtime_instance_id", request["runtime_instance_id"]}, {"status", "accepted"}}; + if (incoming == outgoing) { + result["status"] = "noop"; + laneOutcomes.emplace(command, LaneOutcome{request, result}); + lastLaneSwitch = result; + return result; + } + uint32_t duration = 0; + std::string error; + obs_source_t *transition = mediaTransition.configured() ? mediaTransition.prepare(lane != onAirLane, duration, error) : nullptr; + if (mediaTransition.configured() && !transition) return {{"error", error}}; + sameLaneScene = obs_source_get_ref(incoming); + sameLaneTarget = lane; + sameLaneCommandId = command; + sameLaneAborted = false; + dualLaneCutPending.store(true); + g_dualLaneControlBridge.set_pending(true); + const bool animated = transition != nullptr; + dualLaneTransition.begin(animated ? pulsar_transition::Kind::Stinger : pulsar_transition::Kind::Cut, + duration, true, transition_finalization_lead_ms(1)); + if (animated) { + prepareDualLaneTransition(transition, outgoing); + if (!obs_transition_start(transition, OBS_TRANSITION_MODE_AUTO, duration, incoming)) error = "TRANSITION_START_FAILED"; + } + const bool queued = error.empty() && obs_view_queue_atomic_swap_with_floor( + programView, 0, animated && lane == onAirLane ? transition : currentScene, + previewView, 0, animated && lane == previewLane ? transition : previewScene, + os_gettime_ns(), animated ? OnDualLaneTransitionStarted : OnLaneSwitchCommitted, this); + if (!queued) { + dualLaneTransition.abort("atomic_swap_rejected"); + mediaTransition.finish(); + obs_source_release(sameLaneScene); sameLaneScene = nullptr; sameLaneTarget = -1; + sameLaneCommandId.clear(); + dualLaneCutPending.store(false); g_dualLaneControlBridge.set_pending(false); + return {{"error", error.empty() ? "ATOMIC_SWAP_REJECTED" : error}}; + } + laneOutcomes.emplace(command, LaneOutcome{request, result}); + lastLaneSwitch = result; + return result; +} + +json PulsarFrontendAPI::abortLaneSwitch(const std::string &commandId) +{ + std::lock_guard lock(dualLaneMutex); + if (sameLaneTarget < 0 || sameLaneCommandId != commandId) return {{"error", "COMMAND_NOT_PENDING"}}; + sameLaneAborted = true; + dualLaneTransition.abort("operator"); + // The frame callback observes the abort flag under this same mutex. If a + // final swap is already queued it restores the old composition; otherwise + // the video tick queues that restoration. Never cancel a global slot from + // a request thread after the matching command might have completed. + return {{"command_id", commandId}, {"status", "aborting"}}; +} + +void PulsarFrontendAPI::OnLaneSwitchCommitted(void *param, uint64_t frameId, uint64_t ptsNs) +{ + auto *self = static_cast(param); + json result; + bool program = false; + bool committed = false; + { + std::lock_guard lock(self->dualLaneMutex); + if (self->sameLaneTarget < 0 || !self->sameLaneScene) return; + program = self->sameLaneTarget == self->onAirLane; + committed = !self->sameLaneAborted && self->replaceLaneCompositionLocked(self->sameLaneTarget, self->sameLaneScene); + if (committed) { + auto *&selection = program ? self->programSelection : self->previewSelection; + obs_source_release(selection); + selection = obs_source_get_ref(self->sameLaneScene); + self->sceneSwitchPreparedCommandId.clear(); + } + auto &outcome = self->laneOutcomes.at(self->sameLaneCommandId); + outcome.result["status"] = committed ? "completed" : self->sameLaneAborted ? "aborted" : "failed"; + outcome.result["frame_id"] = frameId; + outcome.result["pts_ns"] = ptsNs; + outcome.result["role_map"] = {{"on_air", self->onAirLane == 0 ? "A" : "B"}, {"preview", self->previewLane == 0 ? "A" : "B"}}; + if (!committed && !self->sameLaneAborted) outcome.result["failure_reason"] = "COMPOSITION_REPLACE_FAILED"; + result = self->lastLaneSwitch = outcome.result; + self->dualLaneTransition.committed(frameId, ptsNs, os_gettime_ns()); + self->mediaTransition.finish(); + obs_source_release(self->sameLaneScene); self->sameLaneScene = nullptr; + self->sameLaneTarget = -1; self->sameLaneCommandId.clear(); + self->dualLaneTransitionFinalPending = false; + self->dualLaneTransitionStartNs = 0; + self->dualLaneInvariantLocked("same-lane-commit"); + // Keep the public mutation gate closed until the scene-switch CAS + // revisions below have caught up with the frame-boundary replacement. + } + if (committed) { + g_sceneSwitchVendorStorage.laneSceneChanged(program); + if (!program) g_runtimeTelemetry.previewRevisionChanged(); + self->emit(program ? OBS_FRONTEND_EVENT_SCENE_CHANGED : OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); + } + { + std::lock_guard lock(self->dualLaneMutex); + self->dualLaneCutPending.store(false); + g_dualLaneControlBridge.set_pending(false); + } + g_mediaTransitionVendor.emit(result); +} + namespace { // ADR 004 §3.1-3.2 -- boot-time video encoder selection with typed x264 @@ -4785,7 +5040,7 @@ bool PulsarFrontendAPI::setupDualLane(obs_scene_t *templateScene) dualLaneReady = true; dualLaneOperational = true; dualLaneTransitionsEnabled = resolve_dual_lane_transitions(); - if (dualLaneTransitionsEnabled) { + if (dualLaneTransitionsEnabled && !resolve_stinger_asset_path().empty()) { std::string stingerPath = resolve_stinger_asset_path(); const StingerAssetValidation asset = validate_stinger_asset(stingerPath); OBSDataAutoRelease stingerSettings = obs_data_create(); @@ -4810,8 +5065,10 @@ bool PulsarFrontendAPI::setupDualLane(obs_scene_t *templateScene) blog(LOG_WARNING, "[pulsar-dual-lane] stinger unavailable; Stinger requests will fall back to Cut"); } - obs_add_tick_callback(&OnDualLaneTick, this); } + // Host-configured transitions can be enabled after boot, without an env + // flag or an asset. The idle callback uploads only a configured first frame. + obs_add_tick_callback(&OnDualLaneTick, this); if (!dualLaneInvariantLocked("setup")) { dualLaneOperational = false; dualLaneReady = false; @@ -4918,6 +5175,11 @@ bool PulsarFrontendAPI::sceneSwitchTake(const std::string &takeCommandId) bool PulsarFrontendAPI::sceneSwitchAbort(const std::string &takeCommandId) { + { + std::lock_guard lock(dualLaneMutex); + if (sameLaneTarget >= 0 || sceneSwitchPendingTakeId != takeCommandId) + return false; + } // libobs cancels only a still-pending atomic request and drains a racing // graphics callback. Once that callback owns the request it clears the // frontend pending marker, so this post-cancel check never reports a @@ -5037,6 +5299,7 @@ void PulsarFrontendAPI::OnDualLaneTransitionAbortCommitted(void *param, uint64_t self->programVideo != self->previewVideo; const bool invariantValid = self->dualLaneInvariantLocked("transition-abort"); self->dualLaneTransition.abort("operator"); + self->mediaTransition.finish(); self->dualLaneTransitionAbortPending = false; self->dualLaneTransitionFinalPending = false; self->dualLaneTransitionStartNs = 0; @@ -5053,6 +5316,17 @@ void PulsarFrontendAPI::OnDualLaneTransitionAbortCommitted(void *param, uint64_t void PulsarFrontendAPI::dualLaneTransitionTick() { std::lock_guard lock(dualLaneMutex); + if (!dualLaneCutPending.load()) mediaTransition.preload_tick(); + if (sameLaneTarget >= 0 && sameLaneAborted) { + if (!dualLaneTransitionFinalPending) { + // An initial swap may still own the slot. Keep the admission gate + // closed and retry on the next video tick until it drains. + dualLaneTransitionFinalPending = obs_view_queue_atomic_swap_with_floor( + programView, 0, currentScene, previewView, 0, previewScene, + os_gettime_ns(), OnLaneSwitchCommitted, this); + } + return; + } if (!dualLaneTransition.active() || dualLaneTransitionFinalPending) return; @@ -5077,9 +5351,11 @@ void PulsarFrontendAPI::dualLaneTransitionTick() // Program becomes the prepared Preview lane and Preview becomes the old // OnAir lane. The transition source is released by the view at this // boundary; no view/video_t/output/encoder is rebound. + const bool sameLane = sameLaneTarget >= 0; const bool queued = obs_view_queue_atomic_swap_with_floor( - programView, 0, previewScene, previewView, 0, currentScene, - dualLaneTransitionStartNs, OnDualLaneCutCommitted, this); + programView, 0, sameLane ? currentScene : previewScene, + previewView, 0, sameLane ? previewScene : currentScene, + dualLaneTransitionStartNs, sameLane ? OnLaneSwitchCommitted : OnDualLaneCutCommitted, this); if (queued) { dualLaneTransitionFinalPending = true; blog(LOG_INFO, "[pulsar-dual-lane] transition_final_commit_queued kind=%s", @@ -5103,6 +5379,7 @@ bool PulsarFrontendAPI::queueDualLaneCut(obs_source_t *scene) bool telemetryAccepted = false; pulsar_transition::Kind transitionKind = pulsar_transition::Kind::Cut; obs_source_t *transitionSource = nullptr; + uint32_t requestedDuration = static_cast((std::max)(0, transitionDuration)); { std::lock_guard lk(dualLaneMutex); if (!dualLaneReady || !dualLaneOperational || !scene || !dualLaneInvariantLocked("queue-before")) { @@ -5129,6 +5406,17 @@ bool PulsarFrontendAPI::queueDualLaneCut(obs_source_t *scene) return false; } + if (mediaTransition.configured()) { + std::string error; + transitionSource = mediaTransition.prepare(false, requestedDuration, error); + if (!transitionSource) { + blog(LOG_WARNING, "[pulsar-transitions] Take rejected: %s", error.c_str()); + g_runtimeTelemetry.cancelPending(); + return false; + } + transitionKind = pulsar_transition::Kind::Stinger; + } + // Reserve the role pair under the lane mutex, but do not perform any // trace-file I/O while it is held. The reservation blocks public // mutations through the bridge and remains valid until the queue @@ -5142,7 +5430,7 @@ bool PulsarFrontendAPI::queueDualLaneCut(obs_source_t *scene) queuedOnAirLane = onAirLane; queuedPreviewLane = previewLane; - if (dualLaneTransitionsEnabled && currentTransition) { + if (!mediaTransition.configured() && dualLaneTransitionsEnabled && currentTransition) { const char *transitionId = obs_source_get_id(currentTransition); const char *transitionName = obs_source_get_name(currentTransition); if ((transitionId && std::strcmp(transitionId, "obs_stinger_transition") == 0) || @@ -5166,10 +5454,10 @@ bool PulsarFrontendAPI::queueDualLaneCut(obs_source_t *scene) const uint64_t finalizationLeadMs = transition_finalization_lead_ms(finalizationFrames); const bool transitionStarted = - dualLaneTransition.begin(transitionKind, static_cast((std::max)(0, transitionDuration)), + dualLaneTransition.begin(transitionKind, requestedDuration, transitionKind == pulsar_transition::Kind::Cut || transitionAvailable, finalizationLeadMs); - const bool animate = transitionStarted && transitionKind != pulsar_transition::Kind::Cut; + bool animate = transitionStarted && transitionKind != pulsar_transition::Kind::Cut; if (transitionKind != pulsar_transition::Kind::Cut && !animate) { if (transitionKind == pulsar_transition::Kind::Stinger && dualLaneStingerAssetFailure) dualLaneTransition.set_fallback_reason(dualLaneStingerAssetFailure); @@ -5182,13 +5470,21 @@ bool PulsarFrontendAPI::queueDualLaneCut(obs_source_t *scene) if (animate) { prepareDualLaneTransition(transitionSource, queuedOnAir); if (!obs_transition_start(transitionSource, OBS_TRANSITION_MODE_AUTO, - static_cast(transitionDuration), queuedPreview)) { + requestedDuration, queuedPreview)) { dualLaneTransition.abort("transition_start_failed"); + if (mediaTransition.configured()) { + mediaTransition.finish(); + dualLaneCutPending.store(false); + g_dualLaneControlBridge.set_pending(false); + g_runtimeTelemetry.cancelPending(); + return false; + } blog(LOG_WARNING, "[pulsar-dual-lane] transition_fallback kind=%s fallback=cut fallback_to_cut=1 reason=transition_start_failed", pulsar_transition::kind_name(transitionKind)); transitionKind = pulsar_transition::Kind::Cut; transitionSource = nullptr; + animate = false; } } @@ -5218,6 +5514,7 @@ bool PulsarFrontendAPI::queueDualLaneCut(obs_source_t *scene) if (!queued) { if (animate) dualLaneTransition.abort("atomic_swap_rejected"); + mediaTransition.finish(); dualLaneCutPending.store(false); g_dualLaneControlBridge.set_pending(false); } @@ -5310,6 +5607,7 @@ void PulsarFrontendAPI::OnDualLaneCutCommitted(void *param, uint64_t frameId, ui self->dualLaneTransitionFinalPending = false; self->dualLaneTransitionStartNs = 0; } + self->mediaTransition.finish(); if (!self->dualLaneInvariantLocked("commit")) blog(LOG_ERROR, "[pulsar-dual-lane] commit invariant failed"); if (self->rollbackAfterTakes > 0 && self->cutCount >= self->rollbackAfterTakes && @@ -5462,7 +5760,7 @@ bool PulsarFrontendAPI::setup() blog(LOG_INFO, "[pulsar-frontend-stub] native stinger compositing %s (PULSAR_NATIVE_STINGER)", nativeStingerEnabled ? "ENABLED (dormant path active)" : "disabled (default; OBS hard-cut)"); - if (nativeStingerEnabled) { + if (nativeStingerEnabled && !resolve_stinger_asset_path().empty()) { // ---- DORMANT NATIVE PATH (flag ON only, ADR §A4.3) ---- // M10 (ADR 003 Amendment 1 §A1.1): register a STINGER transition source so // SetCurrentSceneTransition{name:"Stinger"} resolves and the active @@ -6327,7 +6625,7 @@ void PulsarFrontendAPI::teardown() // is released. The bridge contains no frontend pointer, so a late proc // lookup cannot dereference this object after teardown. g_dualLaneControlBridge.deactivate(); - if (dualLaneTransitionsEnabled) + if (dualLaneReady) obs_remove_tick_callback(&OnDualLaneTick, this); { std::lock_guard lock(dualLaneMutex); @@ -6372,6 +6670,11 @@ void PulsarFrontendAPI::teardown() dualLaneCutPending.store(false); } obs_view_cancel_atomic_swap(); + mediaTransition.clear(); + if (sameLaneScene) { + obs_source_release(sameLaneScene); + sameLaneScene = nullptr; + } // Unbind every main mixer channel (video on 0, audio on 1/2/3) before // releasing the underlying sources. Otherwise libobs keeps refs past @@ -7240,6 +7543,8 @@ extern "C" void pulsar_frontend_finished_loading(void) blog(LOG_WARNING, "[pulsar-frontend-stub] setup() reported partial failure"); if (g_sceneSwitchVendorStorage.start()) g_sceneSwitchVendor.store(&g_sceneSwitchVendorStorage, std::memory_order_release); + if (!g_mediaTransitionVendor.start()) + blog(LOG_WARNING, "[pulsar-transitions] host API registration failed"); g_api->emit(OBS_FRONTEND_EVENT_FINISHED_LOADING); } @@ -7247,6 +7552,7 @@ extern "C" void pulsar_frontend_shutdown(void) { if (!g_api) return; + g_mediaTransitionVendor.stop(); g_sceneSwitchVendor.store(nullptr, std::memory_order_release); g_sceneSwitchVendorStorage.stop(); // Close the supported WebSocket mutation gate before emitting EXIT. This diff --git a/plugins/pulsar-frontend-stub/src/pulsar-media-transition-config.cpp b/plugins/pulsar-frontend-stub/src/pulsar-media-transition-config.cpp new file mode 100644 index 00000000..826a240c --- /dev/null +++ b/plugins/pulsar-frontend-stub/src/pulsar-media-transition-config.cpp @@ -0,0 +1,42 @@ +#include "pulsar-media-transition.h" +#include +#include +#include +#include + +namespace pulsar_transition { +using json = nlohmann::json; + +bool parse_media_config(const json &value, MediaConfig &out, std::string &error) +{ + if (!value.is_object()) { error = "CONFIG_INVALID"; return false; } + for (auto it = value.begin(); it != value.end(); ++it) + if (it.key() != "path" && it.key() != "cut_point_ms" && it.key() != "volume" && it.key() != "muted") { + error = "CONFIG_INVALID"; return false; + } + if (!value.contains("path") || !value["path"].is_string() || + !value.contains("cut_point_ms") || !value["cut_point_ms"].is_number_integer()) { + error = "CONFIG_INVALID"; return false; + } + const auto path = value["path"].get(); + const auto point = value["cut_point_ms"].get(); + if (path.empty() || path.size() > 32767 || path.find('\0') != std::string::npos || + path.find("://") != std::string::npos || !std::filesystem::u8path(path).is_absolute() || + path.rfind("\\\\", 0) == 0 || path.rfind("//", 0) == 0) { + error = "LOCAL_PATH_REQUIRED"; return false; + } + auto extension = std::filesystem::u8path(path).extension().u8string(); + std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + if (extension != ".webm") { error = "WEBM_REQUIRED"; return false; } + if (point < 1 || point >= 20000) { error = "CUT_POINT_INVALID"; return false; } + if (value.contains("volume") && (!value["volume"].is_number() || + !std::isfinite(value["volume"].get()) || value["volume"].get() < 0 || + value["volume"].get() > 1)) { error = "VOLUME_INVALID"; return false; } + if (value.contains("muted") && !value["muted"].is_boolean()) { error = "MUTED_INVALID"; return false; } + out = {path, static_cast(point), value.value("volume", 1.0f), value.value("muted", false)}; + return true; +} + +} // namespace pulsar_transition diff --git a/plugins/pulsar-frontend-stub/src/pulsar-media-transition.cpp b/plugins/pulsar-frontend-stub/src/pulsar-media-transition.cpp new file mode 100644 index 00000000..978cf0b7 --- /dev/null +++ b/plugins/pulsar-frontend-stub/src/pulsar-media-transition.cpp @@ -0,0 +1,140 @@ +#include "pulsar-media-transition.h" +#include +#include +#include +#include +#include + +namespace pulsar_transition { +using json = nlohmann::json; + +MediaTransition::~MediaTransition() { clear(); } + +void MediaTransition::clear() +{ + finish(); + if (source_) { + obs_source_release(source_); + } + source_ = nullptr; + if (media_) obs_source_release(media_); + media_ = nullptr; + config_ = {}; +} + +bool MediaTransition::configure(const json &value, std::string &error) +{ + if (value.is_null()) { clear(); return true; } + MediaConfig next; + if (!parse_media_config(value, next, error)) return false; + std::error_code ec; + const auto path = std::filesystem::u8path(next.path); + if (!std::filesystem::is_regular_file(path, ec)) { error = "ASSET_MISSING"; return false; } + std::ifstream stream(path, std::ios::binary); + std::array header{}; + stream.read(reinterpret_cast(header.data()), header.size()); + if (stream.gcount() != 4 || header != std::array{0x1a, 0x45, 0xdf, 0xa3}) { + error = "ASSET_INVALID_CONTAINER"; return false; + } + OBSDataAutoRelease settings = obs_data_create(); + obs_data_set_string(settings, "path", next.path.c_str()); + obs_data_set_int(settings, "transition_point", next.cut_point_ms); + obs_data_set_int(settings, "tp_type", 0); + obs_data_set_bool(settings, "hw_decode", false); + obs_data_set_bool(settings, "preload", false); // bounded memory; preload the first frame only + obs_data_set_int(settings, "audio_monitoring", OBS_MONITORING_TYPE_NONE); + auto *replacement = obs_source_create_private("obs_stinger_transition", "PulsarConfiguredStinger", settings); + if (!replacement || !obs_get_source_output_flags("ffmpeg_source")) { + if (replacement) obs_source_release(replacement); + error = "DECODER_UNAVAILABLE"; return false; + } + clear(); + config_ = std::move(next); + source_ = replacement; + return true; +} + +obs_source_t *MediaTransition::media() const +{ + // Stinger creation schedules its update on the video thread: its media + // child does not necessarily exist when Configure returns. Observe that + // child during readiness checks, never wait for graphics under the lane + // mutex. Once retained, it is stable until the next idle Configure/Clear. + if (!media_ && source_) obs_source_enum_full_tree(source_, [](obs_source_t *, obs_source_t *child, void *p) { + if (std::strcmp(obs_source_get_id(child), "ffmpeg_source") == 0) + *static_cast(p) = obs_source_get_ref(child); + }, &media_); + return media_; +} + +uint64_t MediaTransition::media_duration_ns() const +{ + if (!media()) return 0; + calldata_t cd = {}; + proc_handler_call(obs_source_get_proc_handler(media_), "get_duration", &cd); + const int64_t duration = calldata_int(&cd, "duration"); + calldata_free(&cd); + return duration > 0 ? static_cast(duration) : 0; +} + +void MediaTransition::preload_tick() +{ + // Inactive ffmpeg sources retain a decoded first frame, but expose zero + // dimensions until that frame has a texture. Upload it on the video thread + // only: doing this from GetState would invert graphics and lane locks. + if (media() && media_duration_ns() && !obs_source_get_width(media_)) { + calldata_t cd = {}; + proc_handler_call(obs_source_get_proc_handler(media_), "preload_first_frame", &cd); + calldata_free(&cd); + obs_source_show_preloaded_video(media_); + } +} + +std::string MediaTransition::readiness_error() const +{ + if (!source_) return "NOT_CONFIGURED"; + std::error_code ec; + if (!std::filesystem::is_regular_file(std::filesystem::u8path(config_.path), ec)) return "ASSET_MISSING"; + if (!media()) return "MEDIA_NOT_READY"; + if (obs_source_media_get_state(media_) == OBS_MEDIA_STATE_ERROR) return "DECODE_FAILED"; + const auto ns = media_duration_ns(); + if (!ns || !obs_source_get_width(media_) || !obs_source_get_height(media_)) return "MEDIA_NOT_READY"; + // OBS stinger appends 250 ms to finish its media/audio tail. The native + // controller must honor that fixed duration, not truncate it to a UI guess. + if (ns + 250000000ULL > 20000000000ULL) return "DURATION_INVALID"; + if (uint64_t{config_.cut_point_ms} * 1000000ULL >= ns) return "CUT_POINT_OUTSIDE_MEDIA"; + return {}; +} + +json MediaTransition::state() const +{ + const auto error = readiness_error(); + const auto ns = media_duration_ns(); + return {{"configured", source_ != nullptr}, + {"config", source_ ? json{{"path", config_.path}, {"cut_point_ms", config_.cut_point_ms}, + {"volume", config_.volume}, {"muted", config_.muted}} : json::object()}, + {"ready", error.empty()}, {"readiness_error", error}, + {"media_duration_ms", ns / 1000000ULL}, + {"transition_duration_ms", ns ? (ns + 250000000ULL) / 1000000ULL : 0}}; +} + +obs_source_t *MediaTransition::prepare(bool preview, uint32_t &duration_ms, std::string &error) +{ + error = readiness_error(); + if (!error.empty()) return nullptr; + duration_ms = static_cast((media_duration_ns() + 250000000ULL) / 1000000ULL); + obs_source_set_volume(media_, config_.volume); + obs_source_set_muted(media_, preview || config_.muted); + prepared_ = true; + return source_; +} + +void MediaTransition::finish() +{ + // clear() releases A/B but does not notify the stinger implementation. + // Reset its media lifecycle as well, including after an early abort. + if (source_ && prepared_) obs_transition_force_stop(source_); + if (source_) obs_transition_clear(source_); + prepared_ = false; +} +} // namespace pulsar_transition diff --git a/plugins/pulsar-websocket/src/requesthandler/RequestHandler.cpp b/plugins/pulsar-websocket/src/requesthandler/RequestHandler.cpp index 27624e2d..90e40d03 100644 --- a/plugins/pulsar-websocket/src/requesthandler/RequestHandler.cpp +++ b/plugins/pulsar-websocket/src/requesthandler/RequestHandler.cpp @@ -66,8 +66,8 @@ bool IsSafetyStopRequest(const std::string &requestType) // GetState is included because it is the adapter's explicit observation API. // A retry of Take must remain gated: once the frontend has frozen, admitting // it to the vendor would let the adapter mutate state (and report success) -// through the legacy CallVendorRequest ingress. Only Abort (to cancel an -// already admitted operation) and GetState (observation) may bypass. +// through the legacy CallVendorRequest ingress. Only named Abort and the +// explicit state/result observations of these two adapters may bypass. bool IsControlledSceneSwitchPendingBypass(const Request &request) { if (request.RequestType != "CallVendorRequest" || !request.RequestData.is_object()) @@ -82,10 +82,10 @@ bool IsControlledSceneSwitchPendingBypass(const Request &request) if (vendor == request.RequestData.end() || nestedRequest == request.RequestData.end() || !vendor->is_string() || !nestedRequest->is_string()) return false; - if (vendor->get() != "pulsar-scene-switch") - return false; const std::string nested = nestedRequest->get(); - return nested == "Abort" || nested == "GetState"; + if (vendor->get() == "pulsar-transitions") + return nested == "Abort" || nested == "GetState" || nested == "GetResult"; + return vendor->get() == "pulsar-scene-switch" && (nested == "Abort" || nested == "GetState"); } // The public scene/transition request shape remains unchanged. A latency diff --git a/scripts/contracts/scene_control/test_dual_lane_contract.py b/scripts/contracts/scene_control/test_dual_lane_contract.py index 94d419e8..dc626c57 100644 --- a/scripts/contracts/scene_control/test_dual_lane_contract.py +++ b/scripts/contracts/scene_control/test_dual_lane_contract.py @@ -677,8 +677,9 @@ def test_websocket_mutation_gate_is_central_and_fail_closed() -> None: assert 'requestType.rfind("Get", 0) == 0' in handler assert "IsControlledSceneSwitchPendingBypass" in handler assert 'request.RequestType != "CallVendorRequest"' in handler - assert 'vendor->get() != "pulsar-scene-switch"' in handler - assert 'return nested == "Abort" || nested == "GetState"' in handler + assert 'vendor->get() == "pulsar-scene-switch" && (nested == "Abort" || nested == "GetState")' in handler + assert 'if (vendor->get() == "pulsar-transitions")' in handler + assert 'return nested == "Abort" || nested == "GetState" || nested == "GetResult";' in handler assert "vendor->is_string() || !nestedRequest->is_string()" in handler assert "json::value() here" in handler assert "const bool controlledSceneSwitchBypass" in handler @@ -694,13 +695,16 @@ def test_websocket_mutation_gate_is_central_and_fail_closed() -> None: assert "PREVIEW_FROZEN" in handler assert "after the dual-lane rollback freeze" in handler # The gate is central and acquired before handler lookup. The only scene - # switch pending bypass is the exact vendor Abort/GetState pair; the - # finite output-stop allowlist is unrelated to Preview mutations. + # switch pending bypass is the exact vendor Abort/GetState pair plus the + # media adapter's named Abort/GetState/GetResult. The finite output-stop + # allowlist is unrelated to Preview mutations. # Prepare/Take/Dispatch, malformed CallVendorRequest data, and every other # vendor remain gated. A post-freeze Take must fail closed at the gateway. assert 'nested == "Prepare"' not in handler assert 'nested == "Take"' not in handler assert 'nested == "Dispatch"' not in handler + for mutation in ("Configure", "Clear", "SwitchLane"): + assert f'nested == "{mutation}"' not in handler assert handler.index("const bool controlledSceneSwitchBypass") < handler.index("_handlerMap.at") diff --git a/scripts/probe-configurable-transition.py b/scripts/probe-configurable-transition.py new file mode 100644 index 00000000..bfa84e25 --- /dev/null +++ b/scripts/probe-configurable-transition.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python3 +"""Isolated synthetic media probe. No Prism, capture device, RTMP or default asset. + +Requires --exe (a dedicated build), --output (evidence directory), ffmpeg, +numpy and websockets. Generates its own small alpha WebM inside --output and +records only colour scenes and that test tone. Reuses the existing process +lifecycle helper, including the inherited Windows shutdown event. +""" +from __future__ import annotations +import argparse +import asyncio +import base64 +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import subprocess +import sys +import time + +import numpy as np +import websockets + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location("transition_probe_process", ROOT / "scripts/probe-dual-lane.py") +process = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = process +spec.loader.exec_module(process) + + +def fixture(path: Path, color=(230, 180, 35)): + frames = np.zeros((45, 180, 320, 4), dtype=np.uint8) + frames[:, :, :, :3] = color + for i in range(45): + t = i / 30 + alpha = min(1, max(0, (t - .15) / .3), max(0, (1.35 - t) / .3)) + frames[i, :, :, 3] = round(255 * alpha) + subprocess.run(["ffmpeg", "-hide_banner", "-loglevel", "error", "-y", "-f", "rawvideo", "-pix_fmt", "rgba", + "-s", "320x180", "-r", "30", "-i", "pipe:0", "-f", "lavfi", "-i", + "sine=frequency=523:duration=1.5:sample_rate=48000", "-c:v", "libvpx-vp9", "-pix_fmt", "yuva420p", + "-b:v", "0", "-crf", "15", "-auto-alt-ref", "0", "-c:a", "libopus", str(path)], + input=frames.tobytes(), check=True, timeout=30) + + +class Wire: + def __init__(self, ws): + self.ws, self.counter, self.events = ws, 0, [] + + async def call(self, kind, data=None, *, error=False): + self.counter += 1 + rid = str(self.counter) + await self.ws.send(json.dumps({"op": 6, "d": {"requestType": kind, "requestId": rid, "requestData": data or {}}})) + while True: + frame = json.loads(await asyncio.wait_for(self.ws.recv(), 15)) + if frame["op"] == 5: + self.events.append(frame["d"]) + elif frame["op"] == 7 and frame["d"]["requestId"] == rid: + response = frame["d"] + if error: + assert not response["requestStatus"]["result"], response + return response["requestStatus"] + assert response["requestStatus"]["result"], response + return response.get("responseData", {}) + + async def vendor(self, operation, data=None, vendor="pulsar-transitions"): + return (await self.call("CallVendorRequest", {"vendorName": vendor, "requestType": operation, + "requestData": data or {}})).get("responseData", {}) + + async def poll(self, operation, data, predicate, *, vendor="pulsar-transitions", timeout=10): + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + value = await self.vendor(operation, data, vendor) + if predicate(value): + return value + await asyncio.sleep(.04) + raise AssertionError(f"Timed out {operation}: {value}") + + +def decode_recording(path: str): + rgb = subprocess.check_output(["ffmpeg", "-v", "error", "-i", path, "-vf", "scale=32:18", "-f", "rawvideo", "-pix_fmt", "rgb24", "pipe:1"], timeout=30) + frames = np.frombuffer(rgb, np.uint8).reshape((-1, 18, 32, 3)) + centers = frames[:, 9, 16, :].astype(float) + samples = subprocess.check_output(["ffmpeg", "-v", "error", "-i", path, "-vn", "-f", "f32le", "-ac", "1", "pipe:1"], timeout=30) + audio = np.frombuffer(samples, np.float32) + return centers, float(np.sqrt(np.mean(audio ** 2))) if audio.size else 0 + + +async def exercise(args, proc, report): + proc.spawn() + ready = await asyncio.to_thread(proc.wait_for, process.READY_RE, 45) + uri, password = ready.groups() + async with websockets.connect(uri, max_size=16 * 1024 * 1024) as ws: + hello = json.loads(await ws.recv())["d"] + identify = {"rpcVersion": 1, "eventSubscriptions": 0x7ff} + if "authentication" in hello: + auth = hello["authentication"] + secret = base64.b64encode(hashlib.sha256((password + auth["salt"]).encode()).digest()).decode() + identify["authentication"] = base64.b64encode(hashlib.sha256((secret + auth["challenge"]).encode()).digest()).decode() + await ws.send(json.dumps({"op": 1, "d": identify})) + assert json.loads(await ws.recv())["op"] == 2 + wire = Wire(ws) + state = await wire.vendor("GetState") + report["default"] = state + assert state["operational"] and not state["configured"] and not state["ready"], state + runtime = state["runtime_instance_id"] + guard = {"runtime_instance_id": runtime} + # The probe never captures the desktop sound; disable the boot input + # before starting any synthetic recording. + inputs = (await wire.call("GetInputList"))["inputs"] + for item in inputs: + if item["inputKind"].startswith("wasapi_"): + await wire.call("SetInputMute", {"inputName": item["inputName"], "inputMuted": True}) + for name, color in [("red", (220, 40, 40)), ("green", (40, 220, 40)), ("blue", (40, 40, 220))]: + await wire.call("CreateScene", {"sceneName": name}) + r, g, b = color + await wire.call("CreateInput", {"sceneName": name, "inputName": name + "-colour", "inputKind": "color_source_v3", + "inputSettings": {"color": r | (g << 8) | (b << 16) | 0xff000000, "width": 1920, "height": 1080}, "sceneItemEnabled": True}) + await wire.call("SetCurrentProgramScene", {"sceneName": "red"}) + await wire.call("SetStudioModeEnabled", {"studioModeEnabled": True}) + await wire.call("SetCurrentPreviewScene", {"sceneName": "blue"}) + config = {"path": str(args.output / "fixture.webm"), "cut_point_ms": 700, "volume": .5, "muted": False} + assert (await wire.vendor("Configure", {"runtime_instance_id": "wrong-runtime", "config": config}))["error"] == "RUNTIME_MISMATCH" + for bad in [{**config, "path": "https://example.invalid/test.webm"}, {**config, "cut_point_ms": -1}, {**config, "volume": 2}]: + assert "error" in await wire.vendor("Configure", {**guard, "config": bad}) + configured = await wire.vendor("Configure", {**guard, "config": config}) + assert "error" not in configured, configured + state = await wire.poll("GetState", {}, lambda s: s.get("ready")) + report["ready"] = state + print("Decoder ready", flush=True) + + async def switch(command, lane, old, new): + return await wire.vendor("SwitchLane", {**guard, "command_id": command, "lane_id": lane, + "expected_scene_name": old, "scene_name": new}) + + async def result(command): + return await wire.poll("GetResult", {**guard, "command_id": command}, lambda s: s.get("status") in ["completed", "aborted", "failed"]) + + await wire.call("StartRecord") + await asyncio.sleep(.35) + initial_v1 = await wire.vendor("GetState", vendor="pulsar-scene-switch") + accepted = await switch("same-program", "A", "red", "green") + assert accepted["status"] == "accepted", accepted + busy = await wire.vendor("GetState") + assert busy["busy"] + assert (await wire.vendor("Abort", {**guard, "command_id": "unrelated"}))["error"] == "COMMAND_NOT_PENDING" + for operation in ["Configure", "Clear", "SwitchLane"]: + frozen = await wire.call("CallVendorRequest", {"vendorName": "pulsar-transitions", "requestType": operation, + "requestData": {**guard, "config": config}}, error=True) + assert "PREVIEW_FROZEN" in frozen.get("comment", ""), frozen + completed = await result("same-program") + assert completed["status"] == "completed" and completed["role_map"] == state["role_map"], completed + assert completed["frame_id"] > 0 and completed["pts_ns"] > 0 + after_v1 = await wire.vendor("GetState", vendor="pulsar-scene-switch") + assert after_v1["revisions"]["program"] == initial_v1["revisions"]["program"] + 1 + assert after_v1["revisions"]["role_map"] == initial_v1["revisions"]["role_map"] + assert (await wire.call("GetCurrentProgramScene"))["currentProgramSceneName"] == "green" + assert (await switch("same-program", "A", "red", "green"))["status"] == "completed" + assert (await switch("same-program", "A", "red", "blue"))["error"] == "IDEMPOTENCY_CONFLICT" + await asyncio.sleep(.35) + recording = (await wire.call("StopRecord"))["outputPath"] + centers, rms = decode_recording(recording) + assert np.linalg.norm(centers[0] - (220, 40, 40)) < 30, centers[0] + assert np.linalg.norm(centers[-1] - (40, 220, 40)) < 30, centers[-1] + assert np.any(np.linalg.norm(centers - (230, 180, 35), axis=1) < 30), centers.tolist() + # Partial-alpha frames must reveal the outgoing and incoming colours; + # an opaque or absent overlay cannot satisfy both blend windows. + assert np.count_nonzero((centers[:, 0] > 180) & (centers[:, 1] > 65) & (centers[:, 1] < 150)) >= 2 + assert np.count_nonzero((centers[:, 0] > 65) & (centers[:, 0] < 180) & (centers[:, 1] > 150)) >= 2 + assert np.min(np.max(centers, axis=1)) > 90, "black frame" + assert rms > .002, ("missing Program transition audio", rms) + report["same_program"] = {"result": completed, "recording": recording, "frames": len(centers), "audio_rms": rms, + "centers": centers.tolist()} + print("Same-lane Program and alpha/audio passed", flush=True) + + # Preview animation must neither replace Program nor play its stinger + # into Program's common audio route. + await wire.call("StartRecord") + assert (await switch("same-preview", "B", "blue", "red"))["status"] == "accepted" + preview = await result("same-preview") + await asyncio.sleep(.25) + preview_recording = (await wire.call("StopRecord"))["outputPath"] + centers, preview_rms = decode_recording(preview_recording) + assert np.max(np.linalg.norm(centers - (40, 220, 40), axis=1)) < 30 + assert preview_rms < .0001, ("Preview sound leaked", preview_rms) + assert preview["role_map"] == state["role_map"] + report["same_preview"] = {"result": preview, "recording": preview_recording, "audio_rms": preview_rms} + print("Preview isolation passed", flush=True) + + v1 = await wire.vendor("GetState", vendor="pulsar-scene-switch") + base = {"contract": "pulsar.scene-switch.v1", "schema_version": 1, "message_type": "command", + **guard, "intent_id": "test-take", "timeout_ms": 8000} + prep = {**base, "command_type": "Prepare", "command_id": "test-prepare", "expected_revisions": v1["revisions"], + "expected_server_seq": v1["server_seq"], "target": {"lane_id": "B", "scene_id": "red"}} + assert (await wire.vendor("Prepare", prep, "pulsar-scene-switch"))["event_type"] == "PrepareAccepted" + ready_v1 = await wire.poll("GetState", {}, lambda s: s.get("state") == "preview_ready", vendor="pulsar-scene-switch") + take = {**base, "command_type": "Take", "command_id": "test-take-command", "expected_revisions": ready_v1["revisions"], + "expected_server_seq": ready_v1["server_seq"], "prepared_command_id": "test-prepare"} + await wire.call("StartRecord") + await asyncio.sleep(.35) + taken = await wire.vendor("Take", take, "pulsar-scene-switch") + assert taken["event_type"] == "TakeAccepted", taken + v1_done = await wire.poll("GetState", {}, lambda s: s.get("role_map", {}).get("on_air") == "B", vendor="pulsar-scene-switch") + assert (await wire.call("GetCurrentProgramScene"))["currentProgramSceneName"] == "red" + await asyncio.sleep(.35) + take_recording = (await wire.call("StopRecord"))["outputPath"] + centers, take_rms = decode_recording(take_recording) + assert np.linalg.norm(centers[0] - (40, 220, 40)) < 30 + assert np.linalg.norm(centers[-1] - (220, 40, 40)) < 30 + assert np.any(np.linalg.norm(centers - (230, 180, 35), axis=1) < 30) + assert np.min(np.max(centers, axis=1)) > 90 + assert take_rms > .002, "Program audio was not restored after Preview" + report["take"] = {"state": v1_done, "recording": take_recording, "audio_rms": take_rms} + print("Configured Take passed", flush=True) + + # Reconfiguration replaces the decoder only while idle. An abort + # restores the original composition and has a terminal frame/PTS. + replacement = {**config, "path": str(args.output / "second-éffect.webm"), "cut_point_ms": 900, "volume": .2, "muted": True} + assert "error" not in await wire.vendor("Configure", {**guard, "config": replacement}) + await wire.poll("GetState", {}, lambda s: s.get("ready")) + assert (await switch("abort-me", "B", "red", "blue"))["status"] == "accepted" + await asyncio.sleep(.2) + assert (await wire.vendor("Abort", {**guard, "command_id": "abort-me"}))["status"] == "aborting" + aborted = await result("abort-me") + assert aborted["status"] == "aborted" + assert (await wire.call("GetCurrentProgramScene"))["currentProgramSceneName"] == "red" + report["abort"] = aborted + assert (await switch("stale", "B", "blue", "green"))["error"] == "SCENE_MISMATCH" + assert (await switch("alias", "B", "red", "green"))["error"] == "LANE_ALIAS" + + # A different filename/decoder works after an early abort, with the + # host-selected cut point and mute. No implementation-default artwork. + await wire.call("StartRecord") + await asyncio.sleep(.35) + assert (await switch("after-abort", "B", "red", "blue"))["status"] == "accepted" + assert (await result("after-abort"))["status"] == "completed" + await asyncio.sleep(.35) + muted_recording = (await wire.call("StopRecord"))["outputPath"] + centers, muted_rms = decode_recording(muted_recording) + assert np.linalg.norm(centers[0] - (220, 40, 40)) < 30 + assert np.linalg.norm(centers[-1] - (40, 40, 220)) < 30 + assert np.any(np.linalg.norm(centers - (155, 100, 235), axis=1) < 30) + assert np.min(np.max(centers, axis=1)) > 90 + assert muted_rms < .0001 + report["replacement_muted"] = {"state": await wire.vendor("GetState"), "recording": muted_recording, "audio_rms": muted_rms} + + assert "error" not in await wire.vendor("Configure", {**guard, "config": {**replacement, "muted": False}}) + await wire.poll("GetState", {}, lambda s: s.get("ready")) + await wire.call("StartRecord") + await asyncio.sleep(.35) + assert (await switch("lower-gain", "B", "blue", "red"))["status"] == "accepted" + await result("lower-gain") + await asyncio.sleep(.35) + gain_recording = (await wire.call("StopRecord"))["outputPath"] + _, gain_rms = decode_recording(gain_recording) + assert .3 < gain_rms / rms < .5, ("media gain ratio", gain_rms / rms) + report["media_gain"] = {"recording": gain_recording, "audio_rms": gain_rms, "ratio_to_half_gain": gain_rms / rms} + + assert not (await wire.vendor("Clear", guard))["configured"] + assert (await wire.vendor("GetState"))["readiness_error"] == "NOT_CONFIGURED" + await wire.call("StartRecord") + await asyncio.sleep(.35) + assert (await switch("clear-cut", "B", "red", "blue"))["status"] == "accepted" + cut = await result("clear-cut") + await asyncio.sleep(.35) + cut_recording = (await wire.call("StopRecord"))["outputPath"] + centers, cut_rms = decode_recording(cut_recording) + distances = np.minimum(np.linalg.norm(centers - (220, 40, 40), axis=1), np.linalg.norm(centers - (40, 40, 220), axis=1)) + assert np.max(distances) < 30 and cut_rms < .0001 + assert cut["role_map"] == v1_done["role_map"] + report["clear_cut"] = {"result": cut, "recording": cut_recording, "audio_rms": cut_rms} + noop = await switch("noop", "B", "blue", "blue") + assert noop["status"] == "noop" and "frame_id" not in noop + + # Accepted configuration may still be unready; never cut around its + # error. Synchronously rejected replacement preserves the old config. + assert "error" not in await wire.vendor("Configure", {**guard, "config": {**config, "cut_point_ms": 19000}}) + invalid = await wire.poll("GetState", {}, lambda s: s.get("readiness_error") == "CUT_POINT_OUTSIDE_MEDIA") + assert not invalid["ready"] + assert (await switch("unready", "B", "blue", "red"))["error"] == "CUT_POINT_OUTSIDE_MEDIA" + assert (await wire.call("GetCurrentProgramScene"))["currentProgramSceneName"] == "blue" + assert (await wire.vendor("Configure", {**guard, "config": {**config, "path": str(args.output / "invalid.webm")}}))["error"] == "ASSET_INVALID_CONTAINER" + assert (await wire.vendor("GetState"))["config"]["cut_point_ms"] == 19000 + assert (await wire.vendor("Configure", {**guard, "config": {**config, "path": str(args.output / "missing.webm")}}))["error"] == "ASSET_MISSING" + await wire.vendor("Clear", guard) + print("Replacement, abort/replay, mute/gain, clear and invalid media passed", flush=True) + terminal = [e["eventData"]["eventData"] for e in wire.events if e.get("eventType") == "VendorEvent" and + e.get("eventData", {}).get("vendorName") == "pulsar-transitions"] + report["terminal_events"] = terminal + assert any(e.get("command_id") == "same-program" and e.get("status") == "completed" for e in terminal) + take_events = [e["eventData"]["eventData"] for e in wire.events if e.get("eventType") == "VendorEvent" and + e.get("eventData", {}).get("vendorName") == "pulsar-scene-switch" and + e["eventData"].get("eventType") == "TakeCommitted"] + assert len(take_events) == 1 and take_events[0]["take_command_id"] == "test-take-command" + assert take_events[0]["frame_id"] > 0 and take_events[0]["pts_ns"] > 0 + assert take_events[0]["runtime_instance_id"] == runtime + report["take"]["committed_event"] = take_events[0] + report["passed"] = True + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--exe", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + args.output = args.output.resolve() + args.output.mkdir(parents=True, exist_ok=True) + for key in ["PULSAR_STINGER_ASSET", "PULSAR_MIC_DEVICE_ID", "PULSAR_PROCESS_AUDIO_NAME", "PULSAR_TRACE_PATH"]: + os.environ.pop(key, None) + os.environ.update(PULSAR_NATIVE_STINGER="0", PULSAR_DUAL_LANE_TRANSITIONS="0", PULSAR_LEGACY_ALIAS="disabled", + PULSAR_DIRECTSHOW_LEGACY_ALIAS="0", PULSAR_DESKTOP_AUDIO_DEVICE_ID="pulsar-synthetic-test-no-device", + PULSAR_FPS="30", PULSAR_RECORD_CONTAINER="mkv") + fixture(args.output / "fixture.webm") + fixture(args.output / "second-éffect.webm", (155, 100, 235)) + (args.output / "invalid.webm").write_bytes(b"not a WebM") + proc = process.PulsarProcess(args.exe.resolve(), "x264", args.output) + report = {"passed": False} + try: + asyncio.run(exercise(args, proc, report)) + except Exception as exc: + report["failure"] = str(exc) + raise + finally: + try: + proc.shutdown() + report["shutdown"] = {"exit_code": proc.proc.returncode if proc.proc else None, "forced": proc.forced_kill_used} + finally: + (args.output / "result.json").write_text(json.dumps(report, indent=2), encoding="utf-8") + safe = [line for line in proc.lines if "password" not in line.lower() and "PULSAR_READY" not in line] + (args.output / "runtime.log").write_text("\n".join(safe), encoding="utf-8") + print(json.dumps({"passed": report["passed"], "evidence": str(args.output / "result.json")})) + + +if __name__ == "__main__": + main() diff --git a/tests/pulsar-media-transition/CMakeLists.txt b/tests/pulsar-media-transition/CMakeLists.txt new file mode 100644 index 00000000..8d31525f --- /dev/null +++ b/tests/pulsar-media-transition/CMakeLists.txt @@ -0,0 +1,14 @@ +if(TARGET pulsar-frontend-stub) + add_executable(pulsar-media-transition-config-probe config-probe.cpp) + target_compile_features(pulsar-media-transition-config-probe PRIVATE cxx_std_17) + target_include_directories(pulsar-media-transition-config-probe PRIVATE + "${PULSAR_UPSTREAM_DIR}/libobs" "${PULSAR_UPSTREAM_BUILD_DIR}/libobs" + "${PULSAR_UPSTREAM_BUILD_DIR}/config") + target_link_libraries(pulsar-media-transition-config-probe PRIVATE pulsar-frontend-stub) + # The parser is a separate, pure object in the static frontend library; + # this executable does not need libobs DLLs. Match the pipeline's probe artifact. + set_target_properties(pulsar-media-transition-config-probe PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests/nv-probe") + add_test(NAME pulsar-media-transition-config-probe COMMAND pulsar-media-transition-config-probe) + set_tests_properties(pulsar-media-transition-config-probe PROPERTIES LABELS "unit") +endif() diff --git a/tests/pulsar-media-transition/config-probe.cpp b/tests/pulsar-media-transition/config-probe.cpp new file mode 100644 index 00000000..29e671db --- /dev/null +++ b/tests/pulsar-media-transition/config-probe.cpp @@ -0,0 +1,35 @@ +#include "pulsar-media-transition.h" +#include +#include +#include + +int main() +{ + using nlohmann::json; + const auto path = (std::filesystem::temp_directory_path() / "custom.webm").u8string(); + const json good = {{"path", path}, {"cut_point_ms", 880}, {"volume", 0.4}, {"muted", true}}; + pulsar_transition::MediaConfig config; + std::string error; + if (!pulsar_transition::parse_media_config(good, config, error) || config.cut_point_ms != 880 || !config.muted) + throw std::runtime_error("valid host configuration rejected"); + int rejected = 0; + const auto reject = [&](json candidate) { + if (pulsar_transition::parse_media_config(candidate, config, error) || error.empty()) + throw std::runtime_error("invalid host configuration accepted: " + candidate.dump()); + ++rejected; + }; + for (auto value : {json(-1), json(0), json(20000), json(0.5), json(true), json("880"), json(nullptr)}) { + auto candidate = good; candidate["cut_point_ms"] = value; reject(candidate); + } + for (auto value : {json(-0.1), json(1.1), json(true), json("0.5"), json(nullptr)}) { + auto candidate = good; candidate["volume"] = value; reject(candidate); + } + for (auto value : {json("https://example.org/file.webm"), json("relative.webm"), json(""), + json("\\\\server\\share\\file.webm"), json(path + ".mp4"), json(path + std::string(1, '\0'))}) { + auto candidate = good; candidate["path"] = value; reject(candidate); + } + auto candidate = good; candidate["muted"] = 1; reject(candidate); + candidate = good; candidate["arbitrary"] = true; reject(candidate); + reject(json::array()); reject(nullptr); + std::cout << "media configuration: valid custom WebM accepted; " << rejected << " invalid cases rejected\n"; +} From 7fc403f346b7b43bbb89c9a792731e67ec923028 Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:49:32 +0200 Subject: [PATCH 02/17] test(transitions): record Prism-hosted WebM validation Record native same-lane and Take completion through the running Prism client, including interrupted attempts, the Preview bridge limitation, and verified restoration of the original runtime and viewing return. Agent-Role: eleven Agent-Thread: 01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7 Work-Unit: local-20261008-webm-transition Issue: local-20261008-webm-transition Evidence: evidence/local-20261008-webm-transition/eleven/20261008T134800Z-prism-test-summary.json --- .../20261008T134452Z-prism-runtime-test.json | 41 ++ .../20261008T134603Z-prism-runtime-test.json | 41 ++ .../eleven/20261008T134640Z-prism-probe.mjs | 53 +++ .../20261008T134640Z-prism-runtime-test.json | 380 ++++++++++++++++++ .../20261008T134800Z-prism-test-summary.json | 59 +++ .../20261008T134853Z-prism-restoration.json | 39 ++ 6 files changed, 613 insertions(+) create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T134452Z-prism-runtime-test.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T134603Z-prism-runtime-test.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T134640Z-prism-probe.mjs create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T134640Z-prism-runtime-test.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T134800Z-prism-test-summary.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T134853Z-prism-restoration.json diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T134452Z-prism-runtime-test.json b/evidence/local-20261008-webm-transition/eleven/20261008T134452Z-prism-runtime-test.json new file mode 100644 index 00000000..fd80b3b3 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T134452Z-prism-runtime-test.json @@ -0,0 +1,41 @@ +{ + "startedAt": "2026-10-08T13:44:52.074Z", + "provenance": "Running Prism BroadcastEngine client and unmodified PulsarSceneSwitch adapter; development inspector invocation; no UI automation", + "limitations": [ + "Preview UI bridge SetPreviewComposite is absent from tested Pulsar branch", + "No subjective visual or audio verdict", + "Development reloads interrupted prior visual sampling" + ], + "events": [], + "before": { + "pulsarRunning": false, + "pulsarPort": null, + "libobsVersion": null, + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recordingBefore": { + "active": false, + "paused": false + }, + "pid": 20952, + "runtime": "prism-7590f4645ecb4ff297f503521eae7569", + "error": "No vendor was found by that name.", + "passed": false, + "after": { + "pulsarRunning": true, + "pulsarPort": 54237, + "libobsVersion": "32.1.2-47-g76c902175-modified-pulsar", + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recordingAfter": { + "active": false, + "paused": false + }, + "finishedAt": "2026-10-08T13:44:55.917Z" +} \ No newline at end of file diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T134603Z-prism-runtime-test.json b/evidence/local-20261008-webm-transition/eleven/20261008T134603Z-prism-runtime-test.json new file mode 100644 index 00000000..b1c86bd4 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T134603Z-prism-runtime-test.json @@ -0,0 +1,41 @@ +{ + "startedAt": "2026-10-08T13:46:03.162Z", + "provenance": "Running Prism BroadcastEngine client and unmodified PulsarSceneSwitch adapter; development inspector invocation; no UI automation", + "limitations": [ + "Preview UI bridge SetPreviewComposite is absent from tested Pulsar branch", + "No subjective visual or audio verdict", + "Development reloads interrupted prior visual sampling" + ], + "events": [], + "before": { + "pulsarRunning": false, + "pulsarPort": null, + "libobsVersion": null, + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recordingBefore": { + "active": false, + "paused": false + }, + "pid": 45668, + "runtime": "prism-7590f4645ecb4ff297f503521eae7569", + "error": "Runtime unavailable", + "passed": false, + "after": { + "pulsarRunning": true, + "pulsarPort": 64739, + "libobsVersion": "32.1.2-47-g76c902175-modified-pulsar", + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recordingAfter": { + "active": false, + "paused": false + }, + "finishedAt": "2026-10-08T13:46:07.437Z" +} \ No newline at end of file diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T134640Z-prism-probe.mjs b/evidence/local-20261008-webm-transition/eleven/20261008T134640Z-prism-probe.mjs new file mode 100644 index 00000000..873bf237 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T134640Z-prism-probe.mjs @@ -0,0 +1,53 @@ +const root='D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261008-webm-transition/evidence/local-20261008-webm-transition/eleven'; +const socket=new WebSocket((await(await fetch('http://127.0.0.1:9235/json/list')).json()).find(x=>x.type==='node').webSocketDebuggerUrl); +await new Promise((done,fail)=>{socket.onopen=done;socket.onerror=fail}); +let id=0;const pending=new Map();socket.onmessage=e=>{const x=JSON.parse(e.data);if(x.id){pending.get(x.id)?.(x);pending.delete(x.id)}}; +const call=(method,params)=>new Promise(done=>{pending.set(++id,done);socket.send(JSON.stringify({id,method,params}))}); +const fn=(await call('Runtime.evaluate',{expression:`process.getBuiltinModule('module').createRequire(process.cwd()+'/package.json')('electron').ipcMain._invokeHandlers.get('broadcast:status')`})).result.result; +const scopes=(await call('Runtime.getProperties',{objectId:fn.objectId})).result.internalProperties.find(x=>x.name==='[[Scopes]]'); +const list=(await call('Runtime.getProperties',{objectId:scopes.value.objectId,ownProperties:true})).result.result; +const found=[]; +for(const scope of list.filter(x=>/^\d+$/.test(x.name))){ + const vars=(await call('Runtime.getProperties',{objectId:scope.value.objectId,ownProperties:true})).result.result; + for(const [name,dest] of [['engine','__webmTestEngine'],['PulsarSceneSwitch','__webmTestSwitcherClass']]){ + const item=vars.find(x=>x.name===name); + if(item?.value?.objectId){await call('Runtime.callFunctionOn',{objectId:item.value.objectId,functionDeclaration:`function(){globalThis.${dest}=this}`,returnByValue:true});found.push(name)} + } +} +if(found.length!==2)throw new Error('Required Prism owners unavailable'); +async function exercise(root) { + const e=globalThis.__webmTestEngine, fs=process.getBuiltinModule('fs'); + const report={startedAt:new Date().toISOString(),provenance:'Running Prism BroadcastEngine client and unmodified PulsarSceneSwitch adapter; development inspector invocation; no UI automation',limitations:['Preview UI bridge SetPreviewComposite is absent from tested Pulsar branch','No subjective visual or audio verdict','Development reloads interrupted prior visual sampling'],events:[]}; + const stamp=report.startedAt.replace(/[-:]/g,'').replace(/\.\d{3}Z$/,'Z'), file=root+'/'+stamp+'-prism-runtime-test.json'; + const save=()=>{fs.mkdirSync(root,{recursive:true});fs.writeFileSync(file,JSON.stringify(report,null,2))};save(); + let obs,listener; + try { + report.before=e.status();report.recordingBefore=await e.recordStatus(); + if(report.before.state!=='idle'||report.recordingBefore.active)throw new Error('Real output active'); + const p=await e.ensure({});obs=p.client.obs;report.pid=p.child.pid;report.runtime=p.runtimeInstanceId; + const v=async(t,d={})=>(await obs.call('CallVendorRequest',{vendorName:'pulsar-transitions',requestType:t,requestData:d})).responseData; + const state=await v('GetState');if(!state.operational)throw new Error('Runtime unavailable'); + const config={path:'D:/Documents/Zab/Artifacts/2026-10-08/zab-transition-trace-sound/zab-smooth-alpha.webm',cut_point_ms:875,volume:1,muted:false}; + report.configured=await v('Configure',{runtime_instance_id:p.runtimeInstanceId,config}); + for(let i=0;i<100;i++){report.ready=await v('GetState');if(report.ready.ready)break;await new Promise(r=>setTimeout(r,50))} + if(!report.ready.ready)throw new Error('WebM not ready'); + const names=['WebMProbe.Program','WebMProbe.Outgoing','WebMProbe.Incoming']; + const existing=(await obs.call('GetSceneList')).scenes.map(s=>s.sceneName); + for(const [i,name] of names.entries())if(!existing.includes(name)){await obs.call('CreateScene',{sceneName:name});await obs.call('CreateInput',{sceneName:name,inputName:name+'.Color',inputKind:'color_source_v3',inputSettings:{width:1920,height:1080,color:[0xff456232,0xff423524,0xffdddce8][i]},sceneItemEnabled:true})} + await obs.call('SetCurrentProgramScene',{sceneName:names[0]});await obs.call('SetStudioModeEnabled',{studioModeEnabled:true});await obs.call('SetCurrentPreviewScene',{sceneName:names[1]}); + listener=x=>{if(['pulsar-transitions','pulsar-scene-switch'].includes(x.vendorName))report.events.push(x)};obs.on('VendorEvent',listener); + const commandId='prism-native-'+Date.now();const before=await v('GetState'); + report.previewAccepted=await v('SwitchLane',{runtime_instance_id:p.runtimeInstanceId,command_id:commandId,lane_id:before.role_map.preview,expected_scene_name:names[1],scene_name:names[2]});save(); + if(report.previewAccepted.status!=='accepted')throw new Error(JSON.stringify(report.previewAccepted)); + for(let i=0;i<100;i++){report.previewResult=await v('GetResult',{runtime_instance_id:p.runtimeInstanceId,command_id:commandId});if(report.previewResult.status!=='accepted')break;await new Promise(r=>setTimeout(r,40))} + if(report.previewResult.status!=='completed')throw new Error('Preview did not complete'); + report.afterPreview=await obs.call('GetSceneList');save(); + const switcher=new globalThis.__webmTestSwitcherClass(obs,p.runtimeInstanceId); + report.take=await switcher.prepareAndTake(await switcher.getState(),names[1]); + report.afterTake=await obs.call('GetSceneList'); + report.cleared=await v('Clear',{runtime_instance_id:p.runtimeInstanceId}); + report.passed=report.previewResult.frame_id>0&&report.take.frameId>0&&report.afterPreview.currentProgramSceneName===names[0]&&report.afterTake.currentProgramSceneName===names[1]&&!report.cleared.configured; + }catch(error){report.error=error.message;report.passed=false}finally{if(obs&&listener)obs.off('VendorEvent',listener);report.after=e.status();report.recordingAfter=await e.recordStatus();report.finishedAt=new Date().toISOString();save()} + return {file,passed:report.passed,error:report.error,preview:report.previewResult,take:report.take,after:report.after}; +} +const result=await call('Runtime.evaluate',{expression:`(${exercise.toString()})(${JSON.stringify(root)})`,awaitPromise:true,returnByValue:true});console.log(JSON.stringify(result));socket.close(); diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T134640Z-prism-runtime-test.json b/evidence/local-20261008-webm-transition/eleven/20261008T134640Z-prism-runtime-test.json new file mode 100644 index 00000000..c5fa8b2f --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T134640Z-prism-runtime-test.json @@ -0,0 +1,380 @@ +{ + "startedAt": "2026-10-08T13:46:40.795Z", + "provenance": "Running Prism BroadcastEngine client and unmodified PulsarSceneSwitch adapter; development inspector invocation; no UI automation", + "limitations": [ + "Preview UI bridge SetPreviewComposite is absent from tested Pulsar branch", + "No subjective visual or audio verdict", + "Development reloads interrupted prior visual sampling" + ], + "events": [ + { + "eventData": { + "command_id": "prism-native-1791467204610", + "frame_id": 178, + "lane_id": "B", + "pts_ns": 1671760033865882, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "scene_name": "WebMProbe.Incoming", + "status": "completed" + }, + "eventType": "LaneSwitchCompleted", + "vendorName": "pulsar-transitions" + }, + { + "eventData": { + "command_id": "prepare-ad9ea2ab-9c62-4c4d-ab33-f21e5f27d917", + "contract": "pulsar.scene-switch.v1", + "deadline_monotonic_ns": 1671770067878000, + "event_type": "PrepareAccepted", + "intent_id": "prism-0682a771-9e24-4749-af87-16184f0a5453", + "message_type": "event", + "observed_at_monotonic_ns": 1671760067984500, + "payload_sha256": "e8d76b58d83432c5072f1eb7404190362d1768722289b3d985837d4d38f65b18", + "previous_revisions": { + "preview": 1, + "program": 0, + "role_map": 0 + }, + "revisions": { + "preview": 2, + "program": 0, + "role_map": 0 + }, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "schema_version": 1, + "server_seq": 1, + "state": "preparing", + "target_lane_id": "B", + "target_scene_id": "WebMProbe.Outgoing" + }, + "eventType": "PrepareAccepted", + "vendorName": "pulsar-scene-switch" + }, + { + "eventData": { + "command_id": "prepare-ad9ea2ab-9c62-4c4d-ab33-f21e5f27d917", + "contract": "pulsar.scene-switch.v1", + "event_type": "PreviewReady", + "first_frame_id": 0, + "first_pts_ns": 1671760067199214, + "intent_id": "prism-0682a771-9e24-4749-af87-16184f0a5453", + "message_type": "event", + "observed_at_monotonic_ns": 1671760084337600, + "payload_sha256": "e8d76b58d83432c5072f1eb7404190362d1768722289b3d985837d4d38f65b18", + "previous_revisions": { + "preview": 2, + "program": 0, + "role_map": 0 + }, + "revisions": { + "preview": 2, + "program": 0, + "role_map": 0 + }, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "schema_version": 1, + "server_seq": 2, + "state": "preview_ready", + "target_lane_id": "B", + "target_scene_id": "WebMProbe.Outgoing" + }, + "eventType": "PreviewReady", + "vendorName": "pulsar-scene-switch" + }, + { + "eventData": { + "command_id": "take-50308dfc-820a-4516-b243-21824778dee4", + "contract": "pulsar.scene-switch.v1", + "event_type": "TakeAccepted", + "freeze_until_monotonic_ns": 1671770085382800, + "intent_id": "prism-0682a771-9e24-4749-af87-16184f0a5453", + "message_type": "event", + "observed_at_monotonic_ns": 1671760087973900, + "payload_sha256": "3c77d617486d8cdb19240d318769475d2da4c4fa6622a9e981fcacca40e5c0cb", + "previous_revisions": { + "preview": 2, + "program": 0, + "role_map": 0 + }, + "revisions": { + "preview": 2, + "program": 0, + "role_map": 0 + }, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "schema_version": 1, + "server_seq": 3, + "state": "take_accepted", + "take_command_id": "take-50308dfc-820a-4516-b243-21824778dee4", + "target_lane_id": "B", + "target_scene_id": "WebMProbe.Outgoing" + }, + "eventType": "TakeAccepted", + "vendorName": "pulsar-scene-switch" + }, + { + "eventData": { + "command_id": "take-50308dfc-820a-4516-b243-21824778dee4", + "contract": "pulsar.scene-switch.v1", + "event_type": "TakeCommitted", + "frame_id": 318, + "intent_id": "prism-0682a771-9e24-4749-af87-16184f0a5453", + "message_type": "event", + "observed_at_monotonic_ns": 1671762368805500, + "payload_sha256": "3c77d617486d8cdb19240d318769475d2da4c4fa6622a9e981fcacca40e5c0cb", + "preview_lane_id": "A", + "previous_revisions": { + "preview": 2, + "program": 0, + "role_map": 0 + }, + "previous_role_map": { + "on_air": "A", + "preview": "B" + }, + "program_lane_id": "B", + "pts_ns": 1671762367199122, + "revisions": { + "preview": 2, + "program": 1, + "role_map": 1 + }, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "schema_version": 1, + "server_seq": 4, + "source_lane_id": "A", + "state": "ready", + "take_command_id": "take-50308dfc-820a-4516-b243-21824778dee4", + "target_lane_id": "B", + "target_scene_id": "WebMProbe.Outgoing" + }, + "eventType": "TakeCommitted", + "vendorName": "pulsar-scene-switch" + } + ], + "before": { + "pulsarRunning": false, + "pulsarPort": null, + "libobsVersion": null, + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recordingBefore": { + "active": false, + "paused": false + }, + "pid": 27888, + "runtime": "prism-7590f4645ecb4ff297f503521eae7569", + "configured": { + "busy": false, + "config": { + "cut_point_ms": 875, + "muted": false, + "path": "D:/Documents/Zab/Artifacts/2026-10-08/zab-transition-trace-sound/zab-smooth-alpha.webm", + "volume": 1 + }, + "configured": true, + "last_switch": {}, + "media_duration_ms": 0, + "operational": true, + "readiness_error": "MEDIA_NOT_READY", + "ready": false, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "transition_duration_ms": 0 + }, + "ready": { + "busy": false, + "config": { + "cut_point_ms": 875, + "muted": false, + "path": "D:/Documents/Zab/Artifacts/2026-10-08/zab-transition-trace-sound/zab-smooth-alpha.webm", + "volume": 1 + }, + "configured": true, + "last_switch": {}, + "media_duration_ms": 2008, + "operational": true, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "transition_duration_ms": 2258 + }, + "previewAccepted": { + "command_id": "prism-native-1791467204610", + "lane_id": "B", + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "scene_name": "WebMProbe.Incoming", + "status": "accepted" + }, + "previewResult": { + "command_id": "prism-native-1791467204610", + "frame_id": 178, + "lane_id": "B", + "pts_ns": 1671760033865882, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "scene_name": "WebMProbe.Incoming", + "status": "completed" + }, + "afterPreview": { + "currentPreviewSceneName": "WebMProbe.Incoming", + "currentPreviewSceneUuid": "b541d366-6b46-4d79-aaf2-e0f9bcf76842", + "currentProgramSceneName": "WebMProbe.Program", + "currentProgramSceneUuid": "3f38f89b-de73-4edd-b24d-407e37bc4db9", + "scenes": [ + { + "sceneIndex": 0, + "sceneName": "WebMProbe.Incoming", + "sceneUuid": "b541d366-6b46-4d79-aaf2-e0f9bcf76842" + }, + { + "sceneIndex": 1, + "sceneName": "WebMProbe.Outgoing", + "sceneUuid": "0a690606-b056-4127-acce-881adf09a1ca" + }, + { + "sceneIndex": 2, + "sceneName": "WebMProbe.Program", + "sceneUuid": "3f38f89b-de73-4edd-b24d-407e37bc4db9" + }, + { + "sceneIndex": 3, + "sceneName": "Default", + "sceneUuid": "f6963c0f-e5bc-473b-b020-336ae4c817e1" + } + ] + }, + "take": { + "intentId": "prism-0682a771-9e24-4749-af87-16184f0a5453", + "prepareCommandId": "prepare-ad9ea2ab-9c62-4c4d-ab33-f21e5f27d917", + "takeCommandId": "take-50308dfc-820a-4516-b243-21824778dee4", + "lane": "B", + "sceneName": "WebMProbe.Outgoing", + "frameId": 318, + "ptsNs": 1671762367199122, + "state": { + "contract": "pulsar.scene-switch.v1", + "frozen": false, + "idempotency_cache_capacity": 4096, + "idempotency_cache_entries": 2, + "operational": true, + "revisions": { + "preview": 2, + "program": 1, + "role_map": 1 + }, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "schema_version": 1, + "server_seq": 4, + "state": "ready" + } + }, + "afterTake": { + "currentPreviewSceneName": "WebMProbe.Program", + "currentPreviewSceneUuid": "3f38f89b-de73-4edd-b24d-407e37bc4db9", + "currentProgramSceneName": "WebMProbe.Outgoing", + "currentProgramSceneUuid": "0a690606-b056-4127-acce-881adf09a1ca", + "scenes": [ + { + "sceneIndex": 0, + "sceneName": "WebMProbe.Incoming", + "sceneUuid": "b541d366-6b46-4d79-aaf2-e0f9bcf76842" + }, + { + "sceneIndex": 1, + "sceneName": "WebMProbe.Outgoing", + "sceneUuid": "0a690606-b056-4127-acce-881adf09a1ca" + }, + { + "sceneIndex": 2, + "sceneName": "WebMProbe.Program", + "sceneUuid": "3f38f89b-de73-4edd-b24d-407e37bc4db9" + }, + { + "sceneIndex": 3, + "sceneName": "Default", + "sceneUuid": "f6963c0f-e5bc-473b-b020-336ae4c817e1" + } + ] + }, + "cleared": { + "busy": false, + "config": {}, + "configured": false, + "last_switch": { + "command_id": "prism-native-1791467204610", + "frame_id": 178, + "lane_id": "B", + "pts_ns": 1671760033865882, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "scene_name": "WebMProbe.Incoming", + "status": "completed" + }, + "media_duration_ms": 0, + "operational": true, + "readiness_error": "NOT_CONFIGURED", + "ready": false, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "prism-7590f4645ecb4ff297f503521eae7569", + "transition_duration_ms": 0 + }, + "passed": true, + "after": { + "pulsarRunning": true, + "pulsarPort": 64807, + "libobsVersion": "32.1.2-47-g76c902175-modified-pulsar", + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recordingAfter": { + "active": false, + "paused": false + }, + "finishedAt": "2026-10-08T13:46:49.235Z" +} \ No newline at end of file diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T134800Z-prism-test-summary.json b/evidence/local-20261008-webm-transition/eleven/20261008T134800Z-prism-test-summary.json new file mode 100644 index 00000000..5800396e --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T134800Z-prism-test-summary.json @@ -0,0 +1,59 @@ +{ + "tested_commit": "940038696c6108d6d8f528e782243677638560c6", + "authority": "Push Pulsar code and test final WebM through Prism; Computer Use stopped on user request", + "runtime_test": "20261008T134640Z-prism-runtime-test.json", + "candidate_only_files": [ + "bin/64bit/pulsar.exe", + "obs-plugins/64bit/obs-websocket.dll" + ], + "configured_asset": "zab-smooth-alpha.webm", + "asset_sha256": "B39A150FEF3EA1A63B3F9D4C62D21A6A3A2E8A5991C70B8D28ABAB39A645E4BF", + "asset_metadata": { + "audio": "Opus", + "fps": 60, + "height": 1080, + "duration_ms": 2008, + "cut_point_ms": 875, + "width": 1920, + "video": "VP9 with ALPHA_MODE=1" + }, + "checks": [ + { + "risk": "Decoder or timing rejection", + "criterion": "Host configuration of the user WebM", + "proof": "ready=true; duration 2008ms; transition 2258ms", + "command": "Prism BroadcastEngine client -> pulsar-transitions Configure/GetState" + }, + { + "risk": "Wrong lane mutation or missing completion", + "criterion": "Same-lane Preview switch", + "proof": "completed; frame_id 178; Program stayed WebMProbe.Program; role map unchanged", + "command": "Prism client -> SwitchLane/GetResult" + }, + { + "risk": "Acceptance mistaken for completion", + "criterion": "Take with configured media", + "proof": "TakeCommitted; frame_id 318; on_air=B; role_map revision=1", + "command": "Existing Prism PulsarSceneSwitch.prepareAndTake" + }, + { + "risk": "Unrequested default", + "criterion": "No active default asset after clear", + "proof": "configured=false", + "command": "Clear/GetState" + } + ], + "limitations": [ + "Internal Prism native control path tested via development inspector; no Prism source changed", + "Normal Preview UI path could not be validated: this branch lacks the existing local SetPreviewComposite bridge", + "No visual or auditory verdict from this Prism run; earlier isolated native probe covered alpha/audio", + "Concurrent Vite main-process reloads interrupted initial sampling; failed attempts retained" + ], + "restoration": { + "preview": "arm ok; live return; 1 consumer; stream resumed", + "matches_original_backup": true, + "websocket_sha256": "19870A01076E674E3543E3AAF973BE3D067AB9980C7677362AD27719B436C9CE", + "pulsar_sha256": "559C64249BF6E242B527F37F811282389F36989AF90C37196BCB5AB072079DB1", + "outputs": "no stream and no recording started" + } +} diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T134853Z-prism-restoration.json b/evidence/local-20261008-webm-transition/eleven/20261008T134853Z-prism-restoration.json new file mode 100644 index 00000000..13991999 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T134853Z-prism-restoration.json @@ -0,0 +1,39 @@ +{ + "restoredAt": "2026-10-08T13:48:53.804Z", + "status": { + "pulsarRunning": true, + "pulsarPort": 64891, + "libobsVersion": "32.1.2-47-g76c902175-modified-pulsar", + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recording": { + "active": false, + "paused": false + }, + "previewReturn": { + "state": "live", + "consumers": 1, + "outputActive": true, + "source": "PulsarPreviewReturn", + "transport": "media-stream", + "error": null + }, + "sceneNames": [ + "ZabPreviewComposite", + "Default" + ], + "remainingTestScenes": 0, + "tracks": [ + { + "label": "Pulsar Preview Return", + "state": "live" + }, + { + "label": "Pulsar Preview Return", + "state": "live" + } + ] +} \ No newline at end of file From 031ad29f2c1d47c4361627d61bc8ad772c674808 Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:33:38 +0200 Subject: [PATCH 03/17] fix(audio): initialize RTWQ for headless WASAPI capture Own the Windows real-time work queue platform before libobs startup and release it only after source teardown. Reproduce MF_E_SHUTDOWN against the real Windows API and validate repeated WASAPI captures and running Prism. Agent-Role: eleven Agent-Thread: 01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7 Work-Unit: local-20261008-rtwq-lifecycle Issue: local-20261008-rtwq-lifecycle Evidence: evidence/local-20261008-rtwq-lifecycle/eleven/20261008T143300Z-report.md --- CMakeLists.txt | 1 + .../eleven/20261008T142000Z-plan.md | 27 ++++ .../eleven/20261008T142747Z-native-tests.txt | 16 +++ .../20261008T142747Z-wasapi-native.json | 9 ++ .../20261008T142851Z-shutdown-tests.txt | 6 + .../20261008T143211Z-prism-runtime.json | 86 +++++++++++++ .../eleven/20261008T143300Z-report.md | 62 ++++++++++ plugins/README.md | 2 +- plugins/pulsar-headless/CMakeLists.txt | 1 + plugins/pulsar-headless/README.md | 27 +++- plugins/pulsar-headless/main.cpp | 23 ++++ .../pulsar-headless/realtime-work-queue.cpp | 61 +++++++++ plugins/pulsar-headless/realtime-work-queue.h | 15 +++ .../test_graceful_shutdown.py | 19 +++ tests/rtwq-lifecycle/CMakeLists.txt | 17 +++ tests/rtwq-lifecycle/probe-wasapi.py | 117 ++++++++++++++++++ tests/rtwq-lifecycle/rtwq-lifecycle-test.cpp | 60 +++++++++ 17 files changed, 546 insertions(+), 3 deletions(-) create mode 100644 evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142000Z-plan.md create mode 100644 evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142747Z-native-tests.txt create mode 100644 evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142747Z-wasapi-native.json create mode 100644 evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142851Z-shutdown-tests.txt create mode 100644 evidence/local-20261008-rtwq-lifecycle/eleven/20261008T143211Z-prism-runtime.json create mode 100644 evidence/local-20261008-rtwq-lifecycle/eleven/20261008T143300Z-report.md create mode 100644 plugins/pulsar-headless/realtime-work-queue.cpp create mode 100644 plugins/pulsar-headless/realtime-work-queue.h create mode 100644 tests/rtwq-lifecycle/CMakeLists.txt create mode 100644 tests/rtwq-lifecycle/probe-wasapi.py create mode 100644 tests/rtwq-lifecycle/rtwq-lifecycle-test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 90d282a3..95628106 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,6 +152,7 @@ if(WIN32) # cross-root/physical-directory authorities, alias hardening, and # acquire/refusal/release/abandoned-recovery of both lock classes. add_subdirectory(tests/runtime-isolation-probe) + add_subdirectory(tests/rtwq-lifecycle) # #248 -- the producer (C) and DirectShow consumer (C++) must take the # same fail-closed namespace decision before either can touch a named diff --git a/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142000Z-plan.md b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142000Z-plan.md new file mode 100644 index 00000000..3545f45f --- /dev/null +++ b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142000Z-plan.md @@ -0,0 +1,27 @@ +# RTWQ lifecycle correction + +Owner: Eleven, thread `01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7`. +Authority: fix the Pulsar error reported by the user; no ADR; no Computer Use. +Base: `0d13a5ff851c7ffc4304463f61a7b7566b93f0e2` (current fetched main, +including the Preview composite bridge used by the installed 3.0.1 runtime). + +Observed error: `RtwqLockSharedWorkQueue failed (0xC00D3E85)` in the running +Prism/Pulsar journal at `2026-10-08T14:08:45.823Z`. Windows identifies this as +`MF_E_SHUTDOWN`. OBS's graphical host starts RTWQ; Pulsar's headless host does +not. WASAPI assumes that the host owns platform startup and shutdown. + +Reuse decision: preserve the upstream WASAPI implementation and its source +lifecycle. Add the missing Windows platform ownership to the existing +headless bootstrap, following its explicit startup/shutdown convention. +An abort that deliberately skips libobs teardown must also leave RTWQ to +process exit, so pending callbacks cannot lose their platform. + +| Criterion | Risk | Validation | Evidence | +|---|---|---|---| +| Real Windows capture queues are usable throughout host lifetime | Loaded DLL without platform startup yields MF_E_SHUTDOWN | Native probe links production startup/shutdown, queues repeatedly, checks before/after and independent startup ownership | CTest output | +| Shutdown respects active source callbacks | Platform stopped before libobs or on unsafe abort | Existing headless shutdown tests plus targeted ordering checks | Test output and diff | +| Native host remains compatible | Build and plugin integration regression | Build native headless and run isolated WASAPI source lifecycle | Build log and native runtime report | +| User's running Prism is repaired without changing product code or active outputs | Wrong binary or disrupting active output | Verify current runtime provenance and idle state, use a reversible compatible binary, check audio sources and Preview | Runtime hashes and journal readback | + +Exclusions: Prism source, upstream patches, UI automation, external streaming, +recording, default WebM, merge, release and unrelated dirty worktrees. diff --git a/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142747Z-native-tests.txt b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142747Z-native-tests.txt new file mode 100644 index 00000000..7f642237 --- /dev/null +++ b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142747Z-native-tests.txt @@ -0,0 +1,16 @@ +Test project D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261008-rtwq-lifecycle/build-rtwq + Start 8: pulsar-rtwq-lifecycle +1/3 Test #8: pulsar-rtwq-lifecycle ................... Passed 0.06 sec + Start 22: pulsar-audio-callback-gate-test +2/3 Test #22: pulsar-audio-callback-gate-test ......... Passed 0.04 sec + Start 23: pulsar-browser-source-task-state-test +3/3 Test #23: pulsar-browser-source-task-state-test ... Passed 0.04 sec + +100% tests passed, 0 tests failed out of 3 + +Label Time Summary: +integration = 0.06 sec*proc (1 test) +runtime = 0.06 sec*proc (1 test) +unit = 0.08 sec*proc (2 tests) + +Total Test time (real) = 0.23 sec diff --git a/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142747Z-wasapi-native.json b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142747Z-wasapi-native.json new file mode 100644 index 00000000..854b1dc3 --- /dev/null +++ b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142747Z-wasapi-native.json @@ -0,0 +1,9 @@ +{ + "runtime": "rtwq-probe-91b10ecc0b66", + "sourcesInitialized": 6, + "passed": true, + "gracefulShutdown": true, + "rtwqErrors": [], + "exitCode": 0, + "forcedKill": false +} \ No newline at end of file diff --git a/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142851Z-shutdown-tests.txt b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142851Z-shutdown-tests.txt new file mode 100644 index 00000000..9795dab8 --- /dev/null +++ b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T142851Z-shutdown-tests.txt @@ -0,0 +1,6 @@ +C:\Users\Mathias\AppData\Local\Programs\Python\Python311\Lib\site-packages\pytest_asyncio\plugin.py:299: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset. +The event loop scope for asynchronous fixtures will default to the "fixture" caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to "function" scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session" + + warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET)) +.................... [100%] +20 passed in 0.21s diff --git a/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T143211Z-prism-runtime.json b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T143211Z-prism-runtime.json new file mode 100644 index 00000000..3f55a6e4 --- /dev/null +++ b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T143211Z-prism-runtime.json @@ -0,0 +1,86 @@ +{ + "startedAt": "2026-10-08T14:29:27.096Z", + "checkedAt": "2026-10-08T14:32:11.067Z", + "provenance": "Existing Prism BroadcastEngine/native obs-websocket client and local development inspector; no Computer Use or UI automation", + "baseRevision": "0d13a5ff851c7ffc4304463f61a7b7566b93f0e2", + "originalExecutableSha256": "559C64249BF6E242B527F37F811282389F36989AF90C37196BCB5AB072079DB1", + "installedExecutableSha256": "06d7267d44061e6aa5f5192ea94d9d983518d52439a87b23783f9f712341e59b", + "oldPid": 13344, + "pid": 17144, + "status": { + "pulsarRunning": true, + "pulsarPort": 53157, + "libobsVersion": "32.1.2-47-g76c902175-modified-pulsar", + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recording": { + "active": false, + "paused": false + }, + "preview": { + "state": "live", + "consumers": 1, + "outputActive": true, + "source": "PulsarPreviewReturn", + "transport": "media-stream", + "error": null + }, + "sceneRestored": true, + "previewFrames": [ + { + "width": 1920, + "height": 1080, + "readyState": 4, + "newFrames": 60, + "tracksLive": true + }, + { + "width": 1920, + "height": 1080, + "readyState": 4, + "newFrames": 60, + "tracksLive": true + } + ], + "audioKinds": [ + "wasapi_output_capture", + "wasapi_input_capture", + "wasapi_process_output_capture" + ], + "rtwqStartup": [ + "2026-10-08T14:29:40Z INFO 7800HUYUmWMUOJo- pulsar-headless | RTWQ platform started" + ], + "wasapiInitializations": [ + { + "source": "PulsarDesktopAudio" + }, + { + "source": "PulsarMic" + }, + { + "source": "ZabCapture:google-chrome" + }, + { + "source": "ZabCapture:google-chrome" + }, + { + "source": "ZabCapture:google-chrome" + }, + { + "source": "ZabCapture:google-chrome" + } + ], + "runtimeErrorCount": 0, + "rtwqFailures": [], + "journalErrors": [], + "rollback": "D:/Documents/Zab/Artifacts/backups/2026-10-08/pulsar-rtwq-fix/pulsar.exe", + "limits": [ + "Local executable replacement; no release or merge", + "No subjective audio-quality assessment", + "No external broadcast or recording" + ], + "passed": true +} \ No newline at end of file diff --git a/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T143300Z-report.md b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T143300Z-report.md new file mode 100644 index 00000000..d9e77a0c --- /dev/null +++ b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T143300Z-report.md @@ -0,0 +1,62 @@ +# Pulsar RTWQ lifecycle — completion report + +Owner: Eleven (`01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7`). +Work unit: `local-20261008-rtwq-lifecycle`. +Base: `0d13a5ff851c7ffc4304463f61a7b7566b93f0e2`. +Authority: repair the reported Pulsar error, continue the previously authorized +push and local Prism validation; no ADR, no Computer Use. + +## Result + +The native headless entry point now starts the Windows RTWQ platform before +libobs and releases its own reference after libobs has drained sources. +Unsafe browser/frontend teardown aborts continue to leave the platform alive +until process exit. Startup failures are reported before readiness. + +The original incident was `RtwqLockSharedWorkQueue failed (0xC00D3E85)` at +`2026-10-08T14:08:45.823Z`. The native test reproduces this exact HRESULT by +loading RTWorkQ without initializing its platform. WASAPI already assumes +host initialization, as implemented in OBS's graphical entry point. + +## Criteria and evidence + +| Criterion | Observed proof | +|---|---| +| Queue lifetime and balanced platform ownership | `20261008T142747Z-native-tests.txt`: all three native tests pass; RTWQ probe exercises 36 capture queue recreations, three platform lifecycles, idempotency and a separate component's retained startup reference. | +| Safe startup/shutdown ordering | `20261008T142851Z-shutdown-tests.txt`: 20 Python checks pass, including all libobs exits and existing callback/teardown contracts. | +| Actual WASAPI captures and clean native shutdown | `20261008T142747Z-wasapi-native.json`: six output captures initialize, are removed, and the process exits with code 0; no RTWQ error or forced kill. | +| Running Prism repaired | `20261008T143211Z-prism-runtime.json`: installed candidate hash confirmed; RTWQ startup logged; microphone, desktop and Chrome audio initialized; zero native errors; both Preview consumers produce 60 new 1920×1080 frames in one second. Scene selection restored, recording inactive, broadcast idle. | + +## Build provenance + +Built `pulsar-headless`, `rtwq-lifecycle-test`, +`pulsar-audio-callback-gate-test` and `pulsar-browser-source-task-state-test` +using MSVC 19.44 / Windows SDK 10.0.26100.0, RelWithDebInfo. + +The canonical checkout was older than the installed 3.0.1 runtime and remains +untouched. The fix branch was aligned with fetched main, which contains the +existing Preview composite bridge. The native build used read-only existing +import libraries/dependencies and a private copy of headers, with the public +header changes from the repository's existing patches 0049 and 0050 applied. +Those patches are part of the installed release's ABI; they are not new changes +in this fix. No shared upstream header or source was edited. + +The isolated integration bundle used read-only hardlinks to installed DLLs +and a separate copy of the candidate executable. Only `pulsar.exe` was replaced +in the running Prism bundle after its status showed idle and recording off. + +Candidate SHA-256: +`06D7267D44061E6AA5F5192EA94D9D983518D52439A87B23783F9F712341E59B`. + +## Limits and rollback + +This is a local runtime replacement and a source branch, not a merged release. +There was no external broadcast, recording, Prism product edit, default WebM, +UI automation or subjective audio-quality assessment. + +Original executable and hash are preserved under +`D:/Documents/Zab/Artifacts/backups/2026-10-08/pulsar-rtwq-fix/`. +Rollback requires stopping the local native process while outputs are idle, +restoring that executable, and restarting through Prism's existing engine. +Renderer return-stream handles must be released before restart and reacquired +afterward; this was done for the successful local replacement. diff --git a/plugins/README.md b/plugins/README.md index a18054a3..2fe10fac 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -6,7 +6,7 @@ builds them against the same patched OBS headers and libraries. | Component | Built artifact | Ownership | |---|---|---| -| [pulsar-headless](pulsar-headless/README.md) | `pulsar.exe` | Bootstrap, runtime namespace, readiness, logs and teardown. | +| [pulsar-headless](pulsar-headless/README.md) | `pulsar.exe` | Bootstrap, runtime namespace, Windows RTWQ audio platform, readiness, logs and teardown. | | [pulsar-frontend-stub](pulsar-frontend-stub/README.md) | Static library in `pulsar.exe` | Frontend API, hot production lanes/views, encoders, singleton outputs, common audio and scene-switch vendor. | | [pulsar-websocket](pulsar-websocket/README.md) | `obs-websocket.dll` | v5 control/authentication/events and vendor dispatch. | | [pulsar-multi-stream](pulsar-multi-stream/README.md) | `pulsar-multi-stream.dll` | Twitch/YouTube/custom RTMP/local destinations, adaptive bitrate, capabilities, audio and diagnostics. | diff --git a/plugins/pulsar-headless/CMakeLists.txt b/plugins/pulsar-headless/CMakeLists.txt index 6fe12c9e..f83cad87 100644 --- a/plugins/pulsar-headless/CMakeLists.txt +++ b/plugins/pulsar-headless/CMakeLists.txt @@ -64,6 +64,7 @@ add_executable(pulsar-headless log-handler.cpp dir-hardening.cpp runtime-identity.cpp + realtime-work-queue.cpp ) target_include_directories(pulsar-headless PRIVATE diff --git a/plugins/pulsar-headless/README.md b/plugins/pulsar-headless/README.md index 309e7ff4..72af9b7d 100644 --- a/plugins/pulsar-headless/README.md +++ b/plugins/pulsar-headless/README.md @@ -40,6 +40,7 @@ configured listener reports active. ## Responsibility surface - `obs_startup` / `obs_shutdown` lifecycle. +- Windows RTWQ startup before libobs and shutdown after source teardown. - Runtime identity and crash-safe instance/legacy-alias leases. - Default video / audio backends selected for the host platform. - Signal pipe-out so `pulsar-websocket` can subscribe to scene / @@ -50,6 +51,28 @@ configured listener reports active. - Explicit inherited anonymous-event shutdown for the native redirected-stdio harness; the current Node bundle does not expose that control. +## Windows audio platform + +`realtime-work-queue.cpp` owns one `RtwqStartup` reference for the process, +before `obs_startup` can create WASAPI sources. Loading the DLL alone is not +enough: a later `RtwqLockSharedWorkQueue` otherwise returns `MF_E_SHUTDOWN` +(`0xC00D3E85`). Startup failure is reported before `PULSAR_READY`. + +The main thread calls `RtwqShutdown` only after `obs_shutdown`, once source +callbacks have drained. Browser or frontend cleanup failures deliberately +leave both libobs and RTWQ alive until process exit. Repeated start/stop calls +do not change another component's reference count. + +`tests/rtwq-lifecycle` links the production helper against the real Windows +API: it reproduces the original failure, exercises repeated Capture queue +creation, and checks platform reference ownership. The headless shutdown +tests also enforce bootstrap/teardown ordering. + +On a Windows host with a full bundle and a default audio output, +`python tests/rtwq-lifecycle/probe-wasapi.py --work-dir --report ` +creates/removes six actual WASAPI sources in an isolated process, verifies +that no recording/stream is active, and requires graceful native shutdown. + ## Out of scope - UI of any kind. If a debug surface is needed it lives in a separate @@ -62,14 +85,14 @@ configured listener reports active. ## Boot and shutdown contract -The order is namespace/lease acquisition → minimal Qt/logging → libobs and +The order is namespace/lease acquisition → minimal Qt/logging → Windows RTWQ → libobs and video/audio → frontend callback installation → protected WebSocket config → module load/listener check → frontend production state → session/READY/idle markers. READY does not prove that a media source has rendered or a remote stream has reached live. On graceful native shutdown, quiesce WebSocket, drain browser callbacks/tasks, -stop/release frontend outputs/sources, then stop libobs and release leases. +stop/release frontend outputs/sources, then stop libobs, stop RTWQ and release leases. A failed barrier refuses unsafe continuation. Forceful process termination does not exercise these same barriers and must not be described as an MP4 finalization guarantee. diff --git a/plugins/pulsar-headless/main.cpp b/plugins/pulsar-headless/main.cpp index d8316fc3..7515b759 100644 --- a/plugins/pulsar-headless/main.cpp +++ b/plugins/pulsar-headless/main.cpp @@ -29,6 +29,7 @@ #include "log-handler.h" #include "pulsar-frontend-stub.h" #include "runtime-identity.h" +#include "realtime-work-queue.h" #include #include @@ -1294,9 +1295,22 @@ int main(int argc, char **argv) // captured by our durable, redacted log handler. install_pulsar_log_handler(); + // win-wasapi uses the host-owned RTWQ platform, as it does in OBS's GUI + // entry point. Loading RTWorkQ.dll alone does not initialize its queues. + const auto work_queue_result = pulsar_runtime::startup_realtime_work_queue(); + if (work_queue_result != 0) { + blog(LOG_ERROR, "[pulsar-headless] RTWQ startup failed (0x%08X)", + static_cast(work_queue_result)); + return 1; + } +#ifdef _WIN32 + blog(LOG_INFO, "[pulsar-headless] RTWQ platform started"); +#endif + const std::string module_config_path = runtime_state->identity.runtime_dir.string(); if (!obs_startup("en-US", module_config_path.c_str(), nullptr)) { blog(LOG_ERROR, "[pulsar-headless] obs_startup failed"); + pulsar_runtime::shutdown_realtime_work_queue(); return 1; } @@ -1312,11 +1326,13 @@ int main(int argc, char **argv) if (!reset_video()) { obs_shutdown(); + pulsar_runtime::shutdown_realtime_work_queue(); return 1; } if (!reset_audio()) { obs_shutdown(); + pulsar_runtime::shutdown_realtime_work_queue(); return 1; } @@ -1358,6 +1374,7 @@ int main(int argc, char **argv) // clean fail-closed exit. pulsar_frontend_shutdown(); obs_shutdown(); + pulsar_runtime::shutdown_realtime_work_queue(); return 1; } @@ -1402,6 +1419,7 @@ int main(int argc, char **argv) return 1; } obs_shutdown(); + pulsar_runtime::shutdown_realtime_work_queue(); return 1; } @@ -1496,6 +1514,11 @@ int main(int argc, char **argv) } obs_shutdown(); + const auto work_queue_shutdown = pulsar_runtime::shutdown_realtime_work_queue(); + if (work_queue_shutdown != 0) + blog(LOG_WARNING, "[pulsar-headless] RTWQ shutdown failed (0x%08X)", + static_cast(work_queue_shutdown)); + runtime_state->release(); #ifdef _WIN32 close_shutdown_event(); diff --git a/plugins/pulsar-headless/realtime-work-queue.cpp b/plugins/pulsar-headless/realtime-work-queue.cpp new file mode 100644 index 00000000..72204e05 --- /dev/null +++ b/plugins/pulsar-headless/realtime-work-queue.cpp @@ -0,0 +1,61 @@ +#include "realtime-work-queue.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +namespace { +using PlatformCall = HRESULT(STDAPICALLTYPE *)(); +HMODULE module = nullptr; +PlatformCall platform_shutdown = nullptr; +} // namespace +#endif + +namespace pulsar_runtime { + +std::uint32_t startup_realtime_work_queue() +{ +#ifdef _WIN32 + if (module) + return 0; + + HMODULE candidate = LoadLibraryExW(L"RTWorkQ.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!candidate) + return static_cast(HRESULT_FROM_WIN32(GetLastError())); + + auto startup = reinterpret_cast(GetProcAddress(candidate, "RtwqStartup")); + auto shutdown = reinterpret_cast(GetProcAddress(candidate, "RtwqShutdown")); + if (!startup || !shutdown) { + FreeLibrary(candidate); + return static_cast(HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND)); + } + + const HRESULT result = startup(); + if (FAILED(result)) { + FreeLibrary(candidate); + return static_cast(result); + } + + module = candidate; + platform_shutdown = shutdown; +#endif + return 0; +} + +std::uint32_t shutdown_realtime_work_queue() +{ +#ifdef _WIN32 + if (!module) + return 0; + + const HRESULT result = platform_shutdown(); + FreeLibrary(module); + module = nullptr; + platform_shutdown = nullptr; + return static_cast(result); +#else + return 0; +#endif +} + +} // namespace pulsar_runtime diff --git a/plugins/pulsar-headless/realtime-work-queue.h b/plugins/pulsar-headless/realtime-work-queue.h new file mode 100644 index 00000000..a26747cc --- /dev/null +++ b/plugins/pulsar-headless/realtime-work-queue.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace pulsar_runtime { + +// Main-thread, process-level ownership, matching libobs's explicit lifecycle. +// Both calls are idempotent. The return value is a Windows HRESULT (0 elsewhere). +std::uint32_t startup_realtime_work_queue(); + +// Call only after obs_shutdown has drained source callbacks. On unsafe teardown +// aborts, retain the platform until process exit instead of stopping live work. +std::uint32_t shutdown_realtime_work_queue(); + +} // namespace pulsar_runtime diff --git a/tests/pulsar-headless-shutdown/test_graceful_shutdown.py b/tests/pulsar-headless-shutdown/test_graceful_shutdown.py index 1d95ccc6..d65db30f 100644 --- a/tests/pulsar-headless-shutdown/test_graceful_shutdown.py +++ b/tests/pulsar-headless-shutdown/test_graceful_shutdown.py @@ -68,6 +68,25 @@ def test_headless_fences_browser_audio_before_obs_shutdown() -> None: assert browser_fence < frontend_shutdown < obs_shutdown +def test_rtwq_outlives_libobs_and_is_retained_on_unsafe_abort() -> None: + source = (ROOT / "plugins" / "pulsar-headless" / "main.cpp").read_text(encoding="utf-8") + main = source[source.index("int main(") :] + assert main.index("startup_realtime_work_queue()") < main.index("obs_startup(") + # Every completed libobs shutdown releases RTWQ immediately afterwards. + shutdowns = list(re.finditer(r"(?m)^\s*obs_shutdown\(\);", main)) + assert len(shutdowns) == 5 + for call in shutdowns: + assert re.match( + r"\s*(?:const auto work_queue_shutdown = )?pulsar_runtime::shutdown_realtime_work_queue\(\);", + main[call.end() :], + ) + startup_failure = main[main.index('if (!obs_startup(') : main.index('[pulsar-runtime] instance_id=')] + assert "shutdown_realtime_work_queue();" in startup_failure + # Browser/source fences may abort with callbacks still alive. Those returns + # intentionally skip both obs_shutdown and RTWQ teardown until process exit. + assert main.count("shutdown_realtime_work_queue()") == len(shutdowns) + 1 + + def test_accelerated_paint_copies_callback_texture_into_owned_texture() -> None: source = (ROOT / "plugins" / "pulsar-browser" / "browser-client.cpp").read_text( encoding="utf-8" diff --git a/tests/rtwq-lifecycle/CMakeLists.txt b/tests/rtwq-lifecycle/CMakeLists.txt new file mode 100644 index 00000000..d96b0132 --- /dev/null +++ b/tests/rtwq-lifecycle/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.28) + +add_executable(rtwq-lifecycle-test + rtwq-lifecycle-test.cpp + "${CMAKE_CURRENT_SOURCE_DIR}/../../plugins/pulsar-headless/realtime-work-queue.cpp" +) +target_include_directories(rtwq-lifecycle-test PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../../plugins/pulsar-headless" +) +target_link_libraries(rtwq-lifecycle-test PRIVATE Rtworkq) +set_target_properties(rtwq-lifecycle-test PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests/nv-probe" +) +add_test(NAME pulsar-rtwq-lifecycle COMMAND rtwq-lifecycle-test) +set_tests_properties(pulsar-rtwq-lifecycle PROPERTIES TIMEOUT 30 LABELS "runtime;integration") diff --git a/tests/rtwq-lifecycle/probe-wasapi.py b/tests/rtwq-lifecycle/probe-wasapi.py new file mode 100644 index 00000000..7bea97d2 --- /dev/null +++ b/tests/rtwq-lifecycle/probe-wasapi.py @@ -0,0 +1,117 @@ +"""Exercise real WASAPI sources in an isolated full Pulsar runtime, without output.""" + +from __future__ import annotations + +import argparse +import asyncio +import importlib.util +import json +import os +from pathlib import Path +import secrets +import sys +import time + + +ROOT = Path(__file__).resolve().parents[2] + + +async def exercise(probe, process, url: str) -> int: + import websockets + + async with websockets.connect(url) as ws: + await probe.identify(ws, process.password) + inbox = probe.Inbox() + counter = 0 + + async def call(kind: str, data=None): + nonlocal counter + counter += 1 + reply = await probe.request(inbox, ws, kind, f"rtwq-{counter}", data) + if not reply["requestStatus"]["result"]: + raise RuntimeError(f"{kind}: {reply['requestStatus']}") + return reply.get("responseData", {}) + + await call("CreateScene", {"sceneName": "RtwqProbe"}) + for index in range(6): + name = f"RtwqProbeOutput-{index}" + await call("CreateInput", { + "sceneName": "RtwqProbe", "inputName": name, + "inputKind": "wasapi_output_capture", + "inputSettings": {"device_id": "default"}, + "sceneItemEnabled": False, + }) + deadline = time.monotonic() + 10 + while not any(f"initialized (source: {name})" in line for line in process.snapshot()): + if time.monotonic() > deadline: + raise RuntimeError(f"WASAPI source did not initialize: {name}") + await asyncio.sleep(0.05) + await call("RemoveInput", {"inputName": name}) + # Exercise sources created after earlier queues have been released. + await asyncio.sleep(0.15) + await call("RemoveScene", {"sceneName": "RtwqProbe"}) + stream = await call("GetStreamStatus") + recording = await call("GetRecordStatus") + if stream["outputActive"] or recording["outputActive"]: + raise RuntimeError("Unexpected output active in isolated probe") + return 6 + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("exe", type=Path) + parser.add_argument("--work-dir", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + if os.name != "nt": + raise RuntimeError("Windows-only WASAPI integration probe") + spec = importlib.util.spec_from_file_location("rtwq_native_probe", ROOT / "scripts/probe-dual-lane.py") + probe = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = probe + spec.loader.exec_module(probe) + + work = args.work_dir.resolve() + work.mkdir(parents=True, exist_ok=True) + runtime = f"rtwq-probe-{secrets.token_hex(6)}" + os.environ.update({ + "PULSAR_RUNTIME_ROOT": str(work / "runtimes"), + "PULSAR_RUNTIME_DIR": str(work / "runtimes" / runtime), + "PULSAR_LEGACY_ALIAS": "disabled", + "PULSAR_DIRECTSHOW_LEGACY_ALIAS": "0", + }) + process = probe.PulsarProcess(args.exe.resolve(), "x264", work / "recordings", runtime_id=runtime) + report = {"runtime": runtime, "sourcesInitialized": 0, "passed": False} + try: + process.spawn() + process.wait_for_shutdown_control_ready(timeout=45) + ready = process.wait_for(probe.READY_RE, timeout=45) + assert any("RTWQ platform started" in line for line in process.snapshot()), "Missing RTWQ startup" + report["sourcesInitialized"] = asyncio.run(exercise(probe, process, ready.group(1))) + except Exception as error: + report["error"] = str(error) + finally: + try: + process.shutdown() + process.assert_shutdown_clean(require_runtime_lease=True) + report["gracefulShutdown"] = True + except Exception as error: + report["shutdownError"] = str(error) + lines = process.snapshot() + report["rtwqErrors"] = [line for line in lines if "RTWQ" in line and ("failed" in line or "ERROR" in line)] + report["exitCode"] = process.proc.returncode if process.proc else None + report["forcedKill"] = process.forced_kill_used + report["passed"] = ( + report["sourcesInitialized"] == 6 + and report.get("gracefulShutdown", False) + and not report["rtwqErrors"] + and not report["forcedKill"] + and "error" not in report + ) + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text(json.dumps(report, indent=2), encoding="utf-8") + print(json.dumps(report)) + return 0 if report["passed"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/rtwq-lifecycle/rtwq-lifecycle-test.cpp b/tests/rtwq-lifecycle/rtwq-lifecycle-test.cpp new file mode 100644 index 00000000..fbc13f43 --- /dev/null +++ b/tests/rtwq-lifecycle/rtwq-lifecycle-test.cpp @@ -0,0 +1,60 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#include "realtime-work-queue.h" + +#include +#include + +static void require(bool condition, const char *message) +{ + if (!condition) { + std::fprintf(stderr, "FAIL: %s\n", message); + std::exit(1); + } +} + +static void expect_capture_queue(bool running) +{ + DWORD task = 0, queue = 0; + const HRESULT result = RtwqLockSharedWorkQueue(L"Capture", 0, &task, &queue); + if (SUCCEEDED(result)) + require(SUCCEEDED(RtwqUnlockWorkQueue(queue)), "release Capture queue"); + if (running) + require(SUCCEEDED(result), "Capture queue usable while host owns RTWQ"); + else + require(result == MF_E_SHUTDOWN, "loaded DLL without startup reproduces 0xC00D3E85"); +} + +int main() +{ + // Linking RTWorkQ loads the DLL, just as another media dependency can do + // in Pulsar. This must not be mistaken for initializing the platform. + expect_capture_queue(false); + std::puts("PASS: reproduced uninitialized Capture queue (0xC00D3E85)"); + + for (int cycle = 0; cycle < 3; ++cycle) { + require(pulsar_runtime::startup_realtime_work_queue() == 0, "host startup"); + require(pulsar_runtime::startup_realtime_work_queue() == 0, "idempotent startup"); + for (int source = 0; source < 12; ++source) + expect_capture_queue(true); + require(pulsar_runtime::shutdown_realtime_work_queue() == 0, "host shutdown"); + expect_capture_queue(false); + require(pulsar_runtime::shutdown_realtime_work_queue() == 0, "idempotent shutdown"); + } + std::puts("PASS: three host lifecycles and 36 Capture queue recreations"); + + // Our matching shutdown must leave a separate dependency's reference live. + require(SUCCEEDED(RtwqStartup()), "independent owner startup"); + require(pulsar_runtime::startup_realtime_work_queue() == 0, "coexisting host startup"); + require(pulsar_runtime::shutdown_realtime_work_queue() == 0, "release host reference only"); + require(pulsar_runtime::shutdown_realtime_work_queue() == 0, "no duplicate platform shutdown"); + expect_capture_queue(true); + require(SUCCEEDED(RtwqShutdown()), "independent owner shutdown"); + expect_capture_queue(false); + std::puts("PASS: independent platform owner preserved; no startup reference leaked"); + return 0; +} From f56fcc2b04ada7bf4123bab21fe7e97907da0a35 Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:45:55 +0200 Subject: [PATCH 04/17] fix(bundle): classify requested child termination as informational Track the launcher's termination request before signaling its child. Preserve unexpected-exit errors and the primary failure on startup cleanup. Validate both bundles and a real isolated native Pulsar shutdown. Agent-Role: eleven Agent-Thread: 01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7 Work-Unit: local-20261008-rtwq-lifecycle Issue: local-20261008-rtwq-lifecycle Evidence: evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144500Z-exit-report.md --- .../eleven/20261008T144000Z-exit-plan.md | 26 +++++++++ .../20261008T144340Z-sdk-native-stop.json | 33 +++++++++++ .../20261008T144440Z-prism-exit-readback.json | 14 +++++ .../eleven/20261008T144500Z-exit-report.md | 57 +++++++++++++++++++ packages/pulsar-bundle-full/README.md | 5 ++ packages/pulsar-bundle-full/src/spawn.ts | 33 ++++++++--- .../pulsar-bundle-full/tests/spawn.test.ts | 50 +++++++++++++++- packages/pulsar-bundle/README.md | 11 +++- packages/pulsar-bundle/src/spawn.ts | 33 ++++++++--- packages/pulsar-bundle/tests/spawn.test.ts | 50 +++++++++++++++- 10 files changed, 293 insertions(+), 19 deletions(-) create mode 100644 evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144000Z-exit-plan.md create mode 100644 evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144340Z-sdk-native-stop.json create mode 100644 evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144440Z-prism-exit-readback.json create mode 100644 evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144500Z-exit-report.md diff --git a/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144000Z-exit-plan.md b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144000Z-exit-plan.md new file mode 100644 index 00000000..f5335883 --- /dev/null +++ b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144000Z-exit-plan.md @@ -0,0 +1,26 @@ +# Continuation: expected process exit diagnostic + +Same owner/thread/work unit as the RTWQ correction. User reported +`PULSAR_PROCESS_EXITED` after that local replacement. + +The journal records one event at `2026-10-08T14:29:27.509Z`, with +`exitCode: null`, `signal: SIGTERM`, during the requested shutdown of PID 13344. +The replacement PID 17144, started at 14:29:40Z, is still running. This is a +false crash diagnostic, not evidence of a new native crash. + +Owner search found the same launcher in the light and full Pulsar bundles. +Their exit handler reports an error unconditionally, including when their +own `terminateChild()` issued the signal. Reuse that existing termination +path to record intent; keep unexpected exits and nonzero failure exits as +errors. No new launcher abstraction or Prism product change is needed. + +| Criterion | Risk | Command/proof | +|---|---|---| +| Requested shutdown produces one informational stop event | Duplicate/fake crash alert | Both bundle spawn suites, concurrent idempotent shutdown | +| Unexpected exit still reports failure | Hiding a real crash based on signal alone | External SIGTERM after ready and spontaneous exit before ready | +| Failed boot retains its primary diagnosis | Cleanup termination produces a second misleading error | Ready timeout and config/connection cleanup path checks | +| Native runtime and current Prism remain healthy | Confusing SDK tests with deployed behavior | Real installed Pulsar spawned/stopped through the patched SDK in isolation; current Prism remains running | + +Authorization: bounded correction of the reported error and previously +authorized push. Exclusions: ADR, Computer Use, Prism source, publication, +live streaming, recording, restart of the healthy current Prism process. diff --git a/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144340Z-sdk-native-stop.json b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144340Z-sdk-native-stop.json new file mode 100644 index 00000000..40669ccc --- /dev/null +++ b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144340Z-sdk-native-stop.json @@ -0,0 +1,33 @@ +{ + "startedAt": "2026-10-08T14:43:39.007Z", + "kind": "real native child through compiled full-bundle SDK", + "passed": true, + "nativePid": 7408, + "libobsVersion": "32.1.2-47-g76c902175-modified-pulsar", + "streamActive": false, + "recordingActive": false, + "stops": [ + { + "schemaVersion": 1, + "severity": "info", + "domain": "service", + "source": "pulsar.runtime", + "code": "PULSAR_PROCESS_STOPPED", + "message": "pulsar.exe stopped after requested termination (code=null, signal=SIGTERM)", + "context": { + "action": "shutdown" + }, + "details": { + "exitCode": null, + "signal": "SIGTERM", + "reason": "shutdown" + } + } + ], + "exitErrors": [], + "childExit": { + "code": null, + "signal": "SIGTERM" + }, + "finishedAt": "2026-10-08T14:43:40.459Z" +} \ No newline at end of file diff --git a/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144440Z-prism-exit-readback.json b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144440Z-prism-exit-readback.json new file mode 100644 index 00000000..32ed33aa --- /dev/null +++ b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144440Z-prism-exit-readback.json @@ -0,0 +1,14 @@ +{ + "checkedAt": "2026-10-08T14:44:40.581Z", + "mainPid": 8640, + "status": { + "pulsarRunning": true, + "pulsarPort": 53157, + "libobsVersion": "32.1.2-47-g76c902175-modified-pulsar", + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recentExitErrors": [] +} \ No newline at end of file diff --git a/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144500Z-exit-report.md b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144500Z-exit-report.md new file mode 100644 index 00000000..7d523b60 --- /dev/null +++ b/evidence/local-20261008-rtwq-lifecycle/eleven/20261008T144500Z-exit-report.md @@ -0,0 +1,57 @@ +# Requested termination classification — completion report + +Owner: Eleven, thread `01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7`. +Continuation of `local-20261008-rtwq-lifecycle`, parent `031ad29f2c1d47c4361627d61bc8ad772c674808`. + +## Incident attribution + +Prism's persisted journal contained one `PULSAR_PROCESS_EXITED` event: +`2026-10-08T14:29:27.509Z`, exit code null, signal SIGTERM, event ID +`d9c7b861-95bb-401a-b3dc-bb23319f198a`. It corresponds to the explicitly +requested shutdown preceding the local RTWQ binary replacement. + +The replacement Pulsar PID 17144 started at `2026-10-08T14:29:40Z` and was +still running under the same Prism PID 8640 at the final readback. There is +no observed new native crash in this incident. + +## Correction and ownership + +Both existing bundle launchers emitted a failure on every child exit. +Their shared-in-contract termination path now records whether it requested +shutdown or startup cleanup before sending a signal. Expected termination +emits `PULSAR_PROCESS_STOPPED` at info severity with the reason and exit +details. Unexpected exits, including external SIGTERM and unsolicited zero +exit, keep the original error. Nonzero exits without a termination signal +remain errors even when cleanup was requested. + +The mirrored light/full launcher files and tests remain identical. Existing +namespace cleanup, readiness rejection, idempotency and signal fallback are +preserved. No Prism source or installed dependency was modified. + +## Validation + +| Command/proof | Observed result | +|---|---| +| `npm run test --workspace=@clodocapeo/pulsar-bundle --workspace=@clodocapeo/pulsar-bundle-full` | 14 tests pass in each package, 28 total. Includes concurrent requested shutdown, external termination after ready, spontaneous code 0/7 exits before ready, and timeout cleanup retaining only its original error. | +| `npm run lint --workspace=@clodocapeo/pulsar-bundle --workspace=@clodocapeo/pulsar-bundle-full` | Both TypeScript checks pass. | +| `npm run build --workspace=@clodocapeo/pulsar-bundle --workspace=@clodocapeo/pulsar-bundle-full` | Both packages compile successfully. | +| `20261008T144340Z-sdk-native-stop.json` | Compiled full SDK starts the real installed native Pulsar in an isolated namespace, observes inactive streaming/recording, and shuts it down concurrently through two callers. Exactly one informational stop event, SIGTERM, no `PULSAR_PROCESS_EXITED`. | +| `20261008T144440Z-prism-exit-readback.json` | Original Prism instance remains healthy and idle, same native port, no recent process-exit error. Independent process readback confirms PID 17144 is unchanged. | +| `git diff --check` | Pass. | + +Test tooling used existing local dependencies; the bundle client was the +installed matching 3.0.1 client through read-only dependency junctions. +The native test used the installed RTWQ-corrected executable and disabled +legacy DirectShow aliases in its independent runtime namespace. + +## Delivery boundary + +The SDK change is a source-branch correction. Its packages were built and +tested but not published or installed into the running Prism session. +Prism will adopt this classification when its bundle dependency is updated +and reloaded. The currently healthy process was not restarted for this test. +The native RTWQ correction from the preceding commit remains active locally. + +Rollback is limited to this commit in the bundle source; there is no new +runtime mutation to roll back. No ADR, Computer Use, external broadcast, +recording or release publication was involved. diff --git a/packages/pulsar-bundle-full/README.md b/packages/pulsar-bundle-full/README.md index 92e4e34b..531af2fd 100644 --- a/packages/pulsar-bundle-full/README.md +++ b/packages/pulsar-bundle-full/README.md @@ -119,6 +119,11 @@ The Node helper terminates the child with a bounded fallback; Windows termination is not a guarantee of graceful CEF/libobs teardown or MP4 finalization. The native shutdown-event harness is a different path. +Requested termination emits informational `PULSAR_PROCESS_STOPPED`; unexpected +exits remain `PULSAR_PROCESS_EXITED` errors. The light bundle's +[lifecycle contract](../pulsar-bundle/README.md#lifecycle-and-restart-policy) +defines the shared classification and startup-cleanup behavior. + Default generated runtime directories are temporary and may be removed after shutdown. Supply persistent recording/log destinations or an explicit application-owned runtime directory. diff --git a/packages/pulsar-bundle-full/src/spawn.ts b/packages/pulsar-bundle-full/src/spawn.ts index 998ca2cf..22e47196 100644 --- a/packages/pulsar-bundle-full/src/spawn.ts +++ b/packages/pulsar-bundle-full/src/spawn.ts @@ -221,8 +221,12 @@ export async function spawn(opts: SpawnOptions = {}): Promise { // Keep failure cleanup deterministic. In particular, a ready timeout must // terminate the child before releasing the generated cwd; otherwise a // failed boot can retain the instance lock and leave a partial namespace. - const terminateChild = async (): Promise => { + let terminationReason: "shutdown" | "startup-cleanup" | null = null; + const terminateChild = async (reason: "shutdown" | "startup-cleanup" = "startup-cleanup"): Promise => { if (child.exitCode !== null || child.signalCode !== null) return; + // Set intent before kill(), whose exit callback can run immediately. + // An external SIGTERM without this ownership remains an unexpected exit. + terminationReason = reason; child.kill(); await new Promise((resolveExit) => { if (child.exitCode !== null || child.signalCode !== null) { @@ -291,11 +295,26 @@ export async function spawn(opts: SpawnOptions = {}): Promise { child.on("exit", (code, signal) => { clearTimeout(watchdog); - emitRuntimeError("PULSAR_PROCESS_EXITED", `pulsar.exe exited prematurely (code=${code}, signal=${signal})`, { - exitCode: code, - signal, - }); - rejectReady(new PulsarRuntimeError("PULSAR_PROCESS_EXITED", `pulsar.exe exited prematurely (code=${code}, signal=${signal})`, { action: "spawn", exitCode: code, signal })); + const expected = terminationReason !== null && + (code === 0 || signal === "SIGTERM" || signal === "SIGKILL"); + if (expected) { + opts.onPrismLog?.({ + schemaVersion: 1, + severity: "info", + domain: "service", + source: "pulsar.runtime", + code: "PULSAR_PROCESS_STOPPED", + message: `pulsar.exe stopped after requested termination (code=${code}, signal=${signal})`, + context: { action: terminationReason === "shutdown" ? "shutdown" : "spawn" }, + details: { exitCode: code, signal, reason: terminationReason }, + }); + } else { + emitRuntimeError("PULSAR_PROCESS_EXITED", `pulsar.exe exited prematurely (code=${code}, signal=${signal})`, { + exitCode: code, + signal, + }); + rejectReady(new PulsarRuntimeError("PULSAR_PROCESS_EXITED", `pulsar.exe exited prematurely (code=${code}, signal=${signal})`, { action: "spawn", exitCode: code, signal })); + } cleanupRuntimeDir(); }); @@ -370,7 +389,7 @@ export async function spawn(opts: SpawnOptions = {}): Promise { } catch { // Already closed -- swallow. } - await terminateChild(); + await terminateChild("shutdown"); cleanupRuntimeDir(); })(); return shutdownPromise; diff --git a/packages/pulsar-bundle-full/tests/spawn.test.ts b/packages/pulsar-bundle-full/tests/spawn.test.ts index c211e478..a79663a4 100644 --- a/packages/pulsar-bundle-full/tests/spawn.test.ts +++ b/packages/pulsar-bundle-full/tests/spawn.test.ts @@ -1,4 +1,5 @@ import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync } from "node:fs"; +import { once } from "node:events"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -47,15 +48,20 @@ describe("spawn()", () => { await expect(spawn({ binariesPath: tmp })).rejects.toThrow(/pulsar\.exe not found/); }); - it("rejects when the executable exits before the ready marker", async () => { + it.each([0, 7])("reports an unsolicited exit with code %i before the ready marker", async (exitCode) => { + const events: Array<{ code: string; severity: string }> = []; // A node command that exits immediately won't print the marker. await expect( spawn({ binariesPath: tmp, - launchCommand: { exe: process.execPath, args: ["-e", "process.exit(0)"] }, + launchCommand: { exe: process.execPath, args: ["-e", `process.exit(${exitCode})`] }, + onPrismLog: (event) => events.push(event), readyTimeoutMs: 2_000, }), ).rejects.toThrow(/exited prematurely|did not signal ready/); + expect(events.filter((event) => event.severity === "error").map((event) => event.code)) + .toEqual(["PULSAR_PROCESS_EXITED"]); + expect(events.some((event) => event.code === "PULSAR_PROCESS_STOPPED")).toBe(false); }); it("forwards stdout/stderr lines via onLog", async () => { @@ -117,6 +123,39 @@ describe("spawn()", () => { expect(handle.client.isConnected()).toBe(false); }); + it("reports concurrent requested shutdown as one informational stop, not a crash", async () => { + const events: Array<{ code: string; severity: string }> = []; + const handle = await spawn({ + binariesPath: tmp, + launchCommand: { exe: process.execPath, args: [FAKE_PULSAR] }, + onPrismLog: (event) => events.push(event), + }); + await Promise.all([handle.shutdown(), handle.shutdown()]); + expect(events.filter((event) => event.code === "PULSAR_PROCESS_STOPPED")) + .toEqual([expect.objectContaining({ severity: "info", context: { action: "shutdown" }, + details: expect.objectContaining({ reason: "shutdown" }) })]); + expect(events.filter((event) => event.severity === "error")).toEqual([]); + }); + + it("still reports an external SIGTERM after readiness as an unexpected exit", async () => { + const events: Array<{ code: string; severity: string }> = []; + const handle = await spawn({ + binariesPath: tmp, + launchCommand: { exe: process.execPath, args: [FAKE_PULSAR] }, + onPrismLog: (event) => events.push(event), + }); + try { + const exited = once(handle.child, "exit"); + expect(handle.child.kill("SIGTERM")).toBe(true); + await exited; + expect(events.filter((event) => event.code === "PULSAR_PROCESS_EXITED")) + .toEqual([expect.objectContaining({ severity: "error" })]); + expect(events.some((event) => event.code === "PULSAR_PROCESS_STOPPED")).toBe(false); + } finally { + await handle.shutdown(); + } + }); + it("gives concurrent children distinct runtime identities and config namespaces", async () => { const handles = await Promise.all( Array.from({ length: 4 }, () => @@ -167,17 +206,24 @@ describe("spawn()", () => { it("cleans a generated namespace when boot times out", async () => { const runtimeRoot = join(tmp, "runtime-root"); + const events: Array<{ code: string; severity: string }> = []; await expect( spawn({ binariesPath: tmp, env: { PULSAR_RUNTIME_ROOT: runtimeRoot }, launchCommand: { exe: process.execPath, args: ["-e", "setInterval(() => {}, 1000)" ] }, readyTimeoutMs: 100, + onPrismLog: (event) => events.push(event), }), ).rejects.toMatchObject({ name: "PulsarRuntimeError", prism: { code: "PULSAR_READY_TIMEOUT" }, }); expect(readdirSync(runtimeRoot)).toHaveLength(0); + expect(events.filter((event) => event.severity === "error").map((event) => event.code)) + .toEqual(["PULSAR_READY_TIMEOUT"]); + expect(events.filter((event) => event.code === "PULSAR_PROCESS_STOPPED")) + .toEqual([expect.objectContaining({ severity: "info", + details: expect.objectContaining({ reason: "startup-cleanup" }) })]); }); }); diff --git a/packages/pulsar-bundle/README.md b/packages/pulsar-bundle/README.md index e87d48f2..129bb9b7 100644 --- a/packages/pulsar-bundle/README.md +++ b/packages/pulsar-bundle/README.md @@ -214,6 +214,14 @@ namespace exists in this SDK. terminates the owned child, waits with a five-second force fallback, and attempts generated-directory cleanup. +An exit caused by the helper's own termination request is emitted through +`onPrismLog` as informational `PULSAR_PROCESS_STOPPED`, with the exit code, +signal and reason (`shutdown` or `startup-cleanup`). A ready-timeout cleanup +therefore preserves the original `PULSAR_READY_TIMEOUT` diagnosis without a +second crash alert. Unrequested exits, including an external SIGTERM or a +spontaneous zero exit, remain `PULSAR_PROCESS_EXITED` errors. A nonzero exit +without a termination signal remains an error even during requested cleanup. + **On Windows, child termination is not proof of graceful libobs shutdown.** Stop/save replay, finalize recording/stream/destinations and retain files first. Closing only the WebSocket does not stop the native engine. @@ -298,7 +306,8 @@ postinstall's version stamp is not that verification. | `PULSAR_RUNTIME_ID_INVALID` | Invalid per-call identity before launch. | | `PULSAR_READY_TIMEOUT` | Read the first native boot failure, namespace collision, listener or module error. | | `PULSAR_CONFIG_MISSING` / `PULSAR_CONFIG_INVALID` | Idle marker did not yield usable per-session config. | -| `PULSAR_PROCESS_ERROR` / `PULSAR_PROCESS_EXITED` | Process launch/exit observation; inspect bounded native logs. | +| `PULSAR_PROCESS_ERROR` / `PULSAR_PROCESS_EXITED` | Process launch failure or unexpected exit; inspect bounded native logs. | +| `PULSAR_PROCESS_STOPPED` (info) | Exit after this helper requested termination; not a graceful media-finalization guarantee. | | Authentication/connect failure | Check listener/config/version consistency; empty password requests generation, not no-auth mode. | | Missing `default.effect` | Check executable-relative data layout and matching native stack, not a shared cwd workaround. | diff --git a/packages/pulsar-bundle/src/spawn.ts b/packages/pulsar-bundle/src/spawn.ts index 998ca2cf..22e47196 100644 --- a/packages/pulsar-bundle/src/spawn.ts +++ b/packages/pulsar-bundle/src/spawn.ts @@ -221,8 +221,12 @@ export async function spawn(opts: SpawnOptions = {}): Promise { // Keep failure cleanup deterministic. In particular, a ready timeout must // terminate the child before releasing the generated cwd; otherwise a // failed boot can retain the instance lock and leave a partial namespace. - const terminateChild = async (): Promise => { + let terminationReason: "shutdown" | "startup-cleanup" | null = null; + const terminateChild = async (reason: "shutdown" | "startup-cleanup" = "startup-cleanup"): Promise => { if (child.exitCode !== null || child.signalCode !== null) return; + // Set intent before kill(), whose exit callback can run immediately. + // An external SIGTERM without this ownership remains an unexpected exit. + terminationReason = reason; child.kill(); await new Promise((resolveExit) => { if (child.exitCode !== null || child.signalCode !== null) { @@ -291,11 +295,26 @@ export async function spawn(opts: SpawnOptions = {}): Promise { child.on("exit", (code, signal) => { clearTimeout(watchdog); - emitRuntimeError("PULSAR_PROCESS_EXITED", `pulsar.exe exited prematurely (code=${code}, signal=${signal})`, { - exitCode: code, - signal, - }); - rejectReady(new PulsarRuntimeError("PULSAR_PROCESS_EXITED", `pulsar.exe exited prematurely (code=${code}, signal=${signal})`, { action: "spawn", exitCode: code, signal })); + const expected = terminationReason !== null && + (code === 0 || signal === "SIGTERM" || signal === "SIGKILL"); + if (expected) { + opts.onPrismLog?.({ + schemaVersion: 1, + severity: "info", + domain: "service", + source: "pulsar.runtime", + code: "PULSAR_PROCESS_STOPPED", + message: `pulsar.exe stopped after requested termination (code=${code}, signal=${signal})`, + context: { action: terminationReason === "shutdown" ? "shutdown" : "spawn" }, + details: { exitCode: code, signal, reason: terminationReason }, + }); + } else { + emitRuntimeError("PULSAR_PROCESS_EXITED", `pulsar.exe exited prematurely (code=${code}, signal=${signal})`, { + exitCode: code, + signal, + }); + rejectReady(new PulsarRuntimeError("PULSAR_PROCESS_EXITED", `pulsar.exe exited prematurely (code=${code}, signal=${signal})`, { action: "spawn", exitCode: code, signal })); + } cleanupRuntimeDir(); }); @@ -370,7 +389,7 @@ export async function spawn(opts: SpawnOptions = {}): Promise { } catch { // Already closed -- swallow. } - await terminateChild(); + await terminateChild("shutdown"); cleanupRuntimeDir(); })(); return shutdownPromise; diff --git a/packages/pulsar-bundle/tests/spawn.test.ts b/packages/pulsar-bundle/tests/spawn.test.ts index c211e478..a79663a4 100644 --- a/packages/pulsar-bundle/tests/spawn.test.ts +++ b/packages/pulsar-bundle/tests/spawn.test.ts @@ -1,4 +1,5 @@ import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync } from "node:fs"; +import { once } from "node:events"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -47,15 +48,20 @@ describe("spawn()", () => { await expect(spawn({ binariesPath: tmp })).rejects.toThrow(/pulsar\.exe not found/); }); - it("rejects when the executable exits before the ready marker", async () => { + it.each([0, 7])("reports an unsolicited exit with code %i before the ready marker", async (exitCode) => { + const events: Array<{ code: string; severity: string }> = []; // A node command that exits immediately won't print the marker. await expect( spawn({ binariesPath: tmp, - launchCommand: { exe: process.execPath, args: ["-e", "process.exit(0)"] }, + launchCommand: { exe: process.execPath, args: ["-e", `process.exit(${exitCode})`] }, + onPrismLog: (event) => events.push(event), readyTimeoutMs: 2_000, }), ).rejects.toThrow(/exited prematurely|did not signal ready/); + expect(events.filter((event) => event.severity === "error").map((event) => event.code)) + .toEqual(["PULSAR_PROCESS_EXITED"]); + expect(events.some((event) => event.code === "PULSAR_PROCESS_STOPPED")).toBe(false); }); it("forwards stdout/stderr lines via onLog", async () => { @@ -117,6 +123,39 @@ describe("spawn()", () => { expect(handle.client.isConnected()).toBe(false); }); + it("reports concurrent requested shutdown as one informational stop, not a crash", async () => { + const events: Array<{ code: string; severity: string }> = []; + const handle = await spawn({ + binariesPath: tmp, + launchCommand: { exe: process.execPath, args: [FAKE_PULSAR] }, + onPrismLog: (event) => events.push(event), + }); + await Promise.all([handle.shutdown(), handle.shutdown()]); + expect(events.filter((event) => event.code === "PULSAR_PROCESS_STOPPED")) + .toEqual([expect.objectContaining({ severity: "info", context: { action: "shutdown" }, + details: expect.objectContaining({ reason: "shutdown" }) })]); + expect(events.filter((event) => event.severity === "error")).toEqual([]); + }); + + it("still reports an external SIGTERM after readiness as an unexpected exit", async () => { + const events: Array<{ code: string; severity: string }> = []; + const handle = await spawn({ + binariesPath: tmp, + launchCommand: { exe: process.execPath, args: [FAKE_PULSAR] }, + onPrismLog: (event) => events.push(event), + }); + try { + const exited = once(handle.child, "exit"); + expect(handle.child.kill("SIGTERM")).toBe(true); + await exited; + expect(events.filter((event) => event.code === "PULSAR_PROCESS_EXITED")) + .toEqual([expect.objectContaining({ severity: "error" })]); + expect(events.some((event) => event.code === "PULSAR_PROCESS_STOPPED")).toBe(false); + } finally { + await handle.shutdown(); + } + }); + it("gives concurrent children distinct runtime identities and config namespaces", async () => { const handles = await Promise.all( Array.from({ length: 4 }, () => @@ -167,17 +206,24 @@ describe("spawn()", () => { it("cleans a generated namespace when boot times out", async () => { const runtimeRoot = join(tmp, "runtime-root"); + const events: Array<{ code: string; severity: string }> = []; await expect( spawn({ binariesPath: tmp, env: { PULSAR_RUNTIME_ROOT: runtimeRoot }, launchCommand: { exe: process.execPath, args: ["-e", "setInterval(() => {}, 1000)" ] }, readyTimeoutMs: 100, + onPrismLog: (event) => events.push(event), }), ).rejects.toMatchObject({ name: "PulsarRuntimeError", prism: { code: "PULSAR_READY_TIMEOUT" }, }); expect(readdirSync(runtimeRoot)).toHaveLength(0); + expect(events.filter((event) => event.severity === "error").map((event) => event.code)) + .toEqual(["PULSAR_READY_TIMEOUT"]); + expect(events.filter((event) => event.code === "PULSAR_PROCESS_STOPPED")) + .toEqual([expect.objectContaining({ severity: "info", + details: expect.objectContaining({ reason: "startup-cleanup" }) })]); }); }); From 4e29cc1b97bbf6b613cb89c3f4cd8c9d6ad3f74c Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Thu, 8 Oct 2026 18:31:11 +0200 Subject: [PATCH 05/17] fix(transitions): cover persistent Preview scene changes Hold the configured media at its cut while the host updates the persistent Preview composite, then reopen and restore the native view. Keep Program and Take independent; bound abort and timeout recovery. Agent-Role: eleven Agent-Thread: 01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7 Work-Unit: local-20261008-webm-transition Issue: local-20261008-webm-transition Evidence: evidence/local-20261008-webm-transition/eleven/20261008T163100Z-preview-report.md --- docs/contracts/configurable-transitions.md | 50 +- .../20261008T161700Z-preview-runtime.json | 213 +++++++ .../20261008T161930Z-preview-deadline.json | 570 ++++++++++++++++++ ...20261008T162900Z-preview-abort-replay.json | 80 +++ .../20261008T163100Z-build-validation.json | 24 + .../eleven/20261008T163100Z-native-checks.txt | 18 + .../eleven/20261008T163100Z-preview-report.md | 25 + .../20261008T163100Z-scene-contracts.txt | 7 + .../include/pulsar-media-transition.h | 2 + .../src/pulsar-frontend-stub.cpp | 181 +++++- .../src/requesthandler/RequestHandler.cpp | 2 +- .../scene_control/test_dual_lane_contract.py | 2 +- 12 files changed, 1159 insertions(+), 15 deletions(-) create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T161700Z-preview-runtime.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T161930Z-preview-deadline.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T162900Z-preview-abort-replay.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T163100Z-build-validation.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T163100Z-native-checks.txt create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T163100Z-preview-report.md create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T163100Z-scene-contracts.txt diff --git a/docs/contracts/configurable-transitions.md b/docs/contracts/configurable-transitions.md index 4ec94cd5..0454fc9b 100644 --- a/docs/contracts/configurable-transitions.md +++ b/docs/contracts/configurable-transitions.md @@ -5,8 +5,8 @@ Owner: `plugins/pulsar-frontend-stub`. The media resource lives in `pulsar-media-transition-config.cpp`; the frontend owns the lane mutex, atomic frame-boundary routing, admission and lifecycle. The `pulsar-transitions` obs-websocket vendor and `PulsarClient.transitions` expose the capability to -hosts such as Prism. This increment changes no Prism code and includes no -default media or artwork. +hosts such as Prism. Pulsar includes no default media or artwork; host UI and +persistence are owned by the host repository. This capability uses Pulsar's existing dual-lane mode (`PULSAR_DUAL_LANE_ENABLED=1`). Its existing activation and rollback gates @@ -30,7 +30,8 @@ takes `{}` and returns: "media_duration_ms": 0, "transition_duration_ms": 0, "role_map": {"on_air": "A", "preview": "B"}, - "last_switch": {} + "last_switch": {}, + "preview_overlay": {} } ``` @@ -112,10 +113,11 @@ The destination must be an existing OBS scene, distinct from the other lane's selected scene and both physical lane roots. A same-scene request returns `status: "noop"`. The caller stages the complete target graph and establishes its source readiness before sending this request. Pulsar cannot infer a -scene replacement occurring inside a persistent browser/DOM source: a future -host integration must stage a distinct composition and invoke this API, not -mutate the visible page before asking for a transition. Plain scene selection -and Prepare remain preparation operations; they do not start this effect. +scene replacement occurring inside a persistent browser/DOM source. Hosts +using that model must use the held-cover Preview operation below before +mutating the visible wire, or stage a distinct composition for `SwitchLane`. +Plain scene selection and Prepare remain preparation operations; they do not +start an effect by themselves. One transition is admitted at a time. Scene/input/settings mutations are frozen from acceptance until the terminal frame callback. An outstanding @@ -141,8 +143,38 @@ source when another client may have submitted a later operation. `Abort` takes the runtime and command IDs, responds `status: "aborting"`, and restores the outgoing composition at a native frame boundary. Await the terminal result. It cannot cancel another command or a v1 Take. Use the -existing scene-switch Abort for a Take. GetState/GetResult/Abort are the only -new operations allowed through the pending-mutation gate. +existing scene-switch Abort for a Take. GetState/GetResult/GetPreviewResult/Abort +are the transition operations allowed through the pending-mutation gate. + +## Persistent cockpit Preview cover + +Prism's cockpit rail updates one `ZabPreviewComposite` in place. It uses +`BeginPreview` with `{runtime_instance_id, command_id}` rather than staging a +second OBS scene. Pulsar creates a private scene containing the bound composite +and the configured decoder child, and attaches it only to PreviewView at an +atomic frame boundary. Program's source, output and audio remain independent; +the Preview media child is muted. Beginning requires a ready configured +decoder and rejects concurrent Take, same-lane transition or prepared Take. + +Poll `GetPreviewResult` with the same runtime/command IDs. The states are +`starting`, `closing`, then `covered`: Pulsar pauses the media when its clock +reaches the configured cut point, within one video tick. The result reports +`cut_time_ms`. The host may now change Orion's persistent Preview wire and +reconcile native sources while the cover remains rendered. Opacity at this +point remains the media author's responsibility. `SetPreviewComposite` +acknowledges the existing composite without removing an active cover. + +Call `EndPreview` with `{runtime_instance_id, command_id, abort: false}` once +the incoming scene is ready. It unpauses the media (`opening`) and restores the +original composite when playback ends. `completed` contains the actual +restoration `frame_id`/`pts_ns`; the start frame is also reported. An early +failure or superseded host request uses `abort: true` and awaits `aborted` at +the restoration frame. Normal release before `covered` is refused. Configure, +Clear and other transitions remain busy throughout this operation; Preview +source reconciliation remains allowed under the cover. If the host disappears +or decoding fails, Pulsar restores the view itself within the configured media +duration plus 20 seconds. The most recent Preview outcome is retained for +readback and command replay, separate from `SwitchLane` outcomes. Vendor validation failures return `{ "error": "CODE" }`; transport admission failures use the existing obs-websocket error envelope. Stable codes include diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T161700Z-preview-runtime.json b/evidence/local-20261008-webm-transition/eleven/20261008T161700Z-preview-runtime.json new file mode 100644 index 00000000..47d0c566 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T161700Z-preview-runtime.json @@ -0,0 +1,213 @@ +{ + "runtime": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "programBefore": { + "currentProgramSceneName": "Default", + "currentProgramSceneUuid": "dcda6008-9844-415e-8f32-8a1883267f16", + "sceneName": "Default", + "sceneUuid": "dcda6008-9844-415e-8f32-8a1883267f16" + }, + "programAfter": { + "currentProgramSceneName": "Default", + "currentProgramSceneUuid": "dcda6008-9844-415e-8f32-8a1883267f16", + "sceneName": "Default", + "sceneUuid": "dcda6008-9844-415e-8f32-8a1883267f16" + }, + "trace": [ + {}, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "closing" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "closing" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "closing" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "closing" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "closing" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "closing" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "closing" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "closing" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "covered" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "covered" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "opening" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "opening" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "opening" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "opening" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "opening" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "opening" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "opening" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "opening" + }, + { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "opening" + } + ], + "completed": { + "channel": "preview", + "command_id": "preview-234d4122-95ae-417c-bd00-81bae190bc1b", + "cut_time_ms": 894, + "frame_id": 4337, + "operational": true, + "pts_ns": 1680654166849542, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 4185, + "start_pts_ns": 1680651633516310, + "status": "completed" + }, + "error": null +} \ No newline at end of file diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T161930Z-preview-deadline.json b/evidence/local-20261008-webm-transition/eleven/20261008T161930Z-preview-deadline.json new file mode 100644 index 00000000..16d46676 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T161930Z-preview-deadline.json @@ -0,0 +1,570 @@ +{ + "accepted": { + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "status": "starting" + }, + "busy": { + "error": "TRANSITION_BUSY" + }, + "elapsedMs": 22393, + "states": [ + { + "elapsed": 10, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "closing" + }, + { + "elapsed": 521, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "closing" + }, + { + "elapsed": 1037, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 1549, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 2050, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 2552, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 3067, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 3581, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 4099, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 4610, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 5110, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 5623, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 6131, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 6639, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 7144, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 7645, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 8160, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 8669, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 9178, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 9683, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 10184, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 10689, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 11192, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 11694, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 12195, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 12711, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 13227, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 13742, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 14255, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 14771, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 15274, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 15786, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 16301, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 16809, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 17314, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 17816, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 18331, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 18834, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 19344, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 19851, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 20353, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 20865, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 21378, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 21879, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "covered" + }, + { + "elapsed": 22393, + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "failure_reason": "PREVIEW_TRANSITION_TIMEOUT_OR_DECODE_ERROR", + "frame_id": 20965, + "operational": true, + "pts_ns": 1680931300171790, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "aborted" + } + ], + "terminal": { + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "failure_reason": "PREVIEW_TRANSITION_TIMEOUT_OR_DECODE_ERROR", + "frame_id": 20965, + "operational": true, + "pts_ns": 1680931300171790, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "aborted" + }, + "programBefore": { + "currentProgramSceneName": "Default", + "currentProgramSceneUuid": "dcda6008-9844-415e-8f32-8a1883267f16", + "sceneName": "Default", + "sceneUuid": "dcda6008-9844-415e-8f32-8a1883267f16" + }, + "programAfter": { + "currentProgramSceneName": "Default", + "currentProgramSceneUuid": "dcda6008-9844-415e-8f32-8a1883267f16", + "sceneName": "Default", + "sceneUuid": "dcda6008-9844-415e-8f32-8a1883267f16" + }, + "after": { + "busy": false, + "config": { + "cut_point_ms": 875, + "muted": false, + "path": "D:/Documents/Zab/Artifacts/2026-10-08/zab-transition-trace-sound/zab-smooth-alpha.webm", + "volume": 1 + }, + "configured": true, + "last_switch": {}, + "media_duration_ms": 2008, + "operational": true, + "preview_overlay": { + "channel": "preview", + "command_id": "preview-deadline-1791476355872", + "cut_time_ms": 894, + "failure_reason": "PREVIEW_TRANSITION_TIMEOUT_OR_DECODE_ERROR", + "frame_id": 20965, + "operational": true, + "pts_ns": 1680931300171790, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "start_frame_id": 19628, + "start_pts_ns": 1680909016839348, + "status": "aborted" + }, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "prism-a37085fb5bc24fd3bc968c0f5c36b61d", + "transition_duration_ms": 2258 + } +} \ No newline at end of file diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T162900Z-preview-abort-replay.json b/evidence/local-20261008-webm-transition/eleven/20261008T162900Z-preview-abort-replay.json new file mode 100644 index 00000000..7880dc8f --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T162900Z-preview-abort-replay.json @@ -0,0 +1,80 @@ +[ + { + "abort": true, + "elapsedMs": 1048, + "covered": { + "channel": "preview", + "command_id": "preview-5c359c01-112d-4f3c-ab75-f1c6bc9ff65e", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-55ee73a4166c4651ba18f3c03e98ce8e", + "start_frame_id": 3364, + "start_pts_ns": 1681403006786890, + "status": "covered" + }, + "terminal": { + "channel": "preview", + "command_id": "preview-5c359c01-112d-4f3c-ab75-f1c6bc9ff65e", + "cut_time_ms": 894, + "frame_id": 3424, + "operational": true, + "pts_ns": 1681404006786850, + "runtime_instance_id": "prism-55ee73a4166c4651ba18f3c03e98ce8e", + "start_frame_id": 3364, + "start_pts_ns": 1681403006786890, + "status": "aborted" + } + }, + { + "abort": true, + "elapsedMs": 1059, + "covered": { + "channel": "preview", + "command_id": "preview-1fa425fc-d3f8-4273-81af-f42bc7749614", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-55ee73a4166c4651ba18f3c03e98ce8e", + "start_frame_id": 3430, + "start_pts_ns": 1681404106786846, + "status": "covered" + }, + "terminal": { + "channel": "preview", + "command_id": "preview-1fa425fc-d3f8-4273-81af-f42bc7749614", + "cut_time_ms": 894, + "frame_id": 3488, + "operational": true, + "pts_ns": 1681405073453474, + "runtime_instance_id": "prism-55ee73a4166c4651ba18f3c03e98ce8e", + "start_frame_id": 3430, + "start_pts_ns": 1681404106786846, + "status": "aborted" + } + }, + { + "abort": false, + "elapsedMs": 2253, + "covered": { + "channel": "preview", + "command_id": "preview-bc1203e1-5852-4f39-9a20-b9ea0155cdb3", + "cut_time_ms": 894, + "operational": true, + "runtime_instance_id": "prism-55ee73a4166c4651ba18f3c03e98ce8e", + "start_frame_id": 3493, + "start_pts_ns": 1681405156786804, + "status": "covered" + }, + "terminal": { + "channel": "preview", + "command_id": "preview-bc1203e1-5852-4f39-9a20-b9ea0155cdb3", + "cut_time_ms": 894, + "frame_id": 3622, + "operational": true, + "pts_ns": 1681407306786718, + "runtime_instance_id": "prism-55ee73a4166c4651ba18f3c03e98ce8e", + "start_frame_id": 3493, + "start_pts_ns": 1681405156786804, + "status": "completed" + } + } +] \ No newline at end of file diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-build-validation.json b/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-build-validation.json new file mode 100644 index 00000000..cb86235a --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-build-validation.json @@ -0,0 +1,24 @@ +{ + "base": "fb29cd7fcf41d0979198df25d4d58b832cf723aa", + "checks": { + "scene_control_contracts": 101, + "native_tests": 4, + "headless_websocket_quiesce": true, + "graceful_cef_fixture": false, + "prior_headless_same_failure": true + }, + "graceful_cef_fixture_limitation": { + "failure": "CEF source creation marker was not observed", + "candidate_log": "build-transition/preview-native-tests-final.txt", + "baseline_log": "build-transition/preview-graceful-baseline.txt", + "baseline_sha256": "e255ca6767c778532b84b1f2775c8aa1f3c30bc578d6c5bdf60aec30b67a4bb0", + "browser_binary_has_lifecycle_marker": { + "obs-browser.dll": false, + "pulsar-browser.dll": false + } + }, + "activated_sha256": { + "bin/64bit/pulsar.exe": "feb36c35f5f238060b26f524f7c26a11e6b3b110f8d438bbe3dc2087fd2ee611", + "obs-plugins/64bit/obs-websocket.dll": "2604e4038f873104aefafd9ec23d24aa4887229d60a04184d009206a7d53288d" + } +} diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-native-checks.txt b/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-native-checks.txt new file mode 100644 index 00000000..7d48b68a --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-native-checks.txt @@ -0,0 +1,18 @@ +Test project D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261008-webm-transition/build-transition + Start 8: pulsar-rtwq-lifecycle +1/4 Test #8: pulsar-rtwq-lifecycle .................. Passed 0.25 sec + Start 15: pulsar-transition-controller-probe +2/4 Test #15: pulsar-transition-controller-probe ..... Passed 0.03 sec + Start 16: pulsar-media-transition-config-probe +3/4 Test #16: pulsar-media-transition-config-probe ... Passed 0.04 sec + Start 25: pulsar-headless-websocket-quiesce +4/4 Test #25: pulsar-headless-websocket-quiesce ...... Passed 14.93 sec + +100% tests passed, 0 tests failed out of 4 + +Label Time Summary: +integration = 15.18 sec*proc (2 tests) +runtime = 15.18 sec*proc (2 tests) +unit = 0.07 sec*proc (2 tests) + +Total Test time (real) = 15.31 sec diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-preview-report.md b/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-preview-report.md new file mode 100644 index 00000000..78c0ad15 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-preview-report.md @@ -0,0 +1,25 @@ +# Persistent Preview transition correction + +Owner: Eleven; existing `local-20261008-webm-transition` branch, continuing the user-authorized scene transition. Host/render proof belongs to Prism. No ADR, default media, stream, Computer Use or user-content recording was introduced. + +Pulsar's staged `SwitchLane` operation did not apply to Prism's in-place Orion Preview updates. The frontend now provides `BeginPreview`, `GetPreviewResult` and `EndPreview`: a private scene overlays the actual bound `ZabPreviewComposite` in PreviewView, pauses the shared media decoder at the cut, then reopens and restores the composite at a native frame boundary. Program's bound scene and audio remain independent. Cover playback mutes only the media child. Existing Take and same-lane transitions are refused while the cover is active; native source reconciliation stays allowed. Abort and a native deadline restore the view even if the host abandons its request. + +The existing decoder and atomic view-swap primitive are reused. `SetPreviewComposite` acknowledges the existing base without stripping a held cover. Outcomes correlate runtime/command IDs and provide start/terminal frame IDs and PTS. The contract documentation lists the lifecycle and distinguishes it from staged lane replacement. + +## Evidence + +- `20261008T161700Z-preview-runtime.json`: closing, held cover and reopening, terminal frame 4337/positive PTS; Program scene identity unchanged. +- `20261008T162900Z-preview-abort-replay.json`: two consecutive aborts, then immediate normal playback, all with terminal frame/PTS; host handles decoder deactivation's pre-admission readiness race. +- `20261008T161930Z-preview-deadline.json`: abandoned cover self-restored in 22393 ms; Configure returns TRANSITION_BUSY during playback, busy false afterwards, Program identity unchanged. +- `20261008T163100Z-scene-contracts.txt`: 101 scene-control contract tests passed. +- `20261008T163100Z-native-checks.txt`: native transition controller, media parser, RTWQ lifecycle and headless WebSocket quiesce all passed. The RTWQ test target was built before its final run. +- `20261008T163100Z-build-validation.json`: activated executable/WebSocket hashes and the pre-existing full CEF fixture limitation. Headless and WebSocket targets built successfully with RelWithDebInfo. +- Prism's final rail-click JSON and cover PNG under its matching evidence unit prove the actual returned video, not just command acceptance or a synthetic scene swap. + +The full CEF graceful-shutdown fixture fails because it does not observe `CEF source creation marker`; both bundled browser DLLs lack its expected `PULSAR_CEF_LIFECYCLE` marker. The previous headless binary with the same browser bundle reproduces exactly the same failure. The test was not weakened, and those DLLs were not replaced as part of this transition fix. Four native tests above and actual idle Prism restarts succeed; do not call the complete CEF fixture green. + +## Activation and rollback + +Only the executable and WebSocket DLL were activated in the user's existing approved Prism bundle. The previous pair is backed up at `Artifacts/backups/2026-10-08/pulsar-preview-transition`. Native runtime identity, leases and expected stop behavior remain intact; final Prism PID 51496 / Pulsar PID 31472 is idle, with no recording, destination or exit error. The selected original scene is restored. + +Rollback is the backed-up native pair after the idle host has stopped, plus the previous Prism snapshot. No main merge, CI success or release publication is claimed. The worktree is retained following the earlier cleanup policy rejection, without an alternate deletion attempt. diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-scene-contracts.txt b/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-scene-contracts.txt new file mode 100644 index 00000000..103f4d89 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T163100Z-scene-contracts.txt @@ -0,0 +1,7 @@ +C:\Users\Mathias\AppData\Local\Programs\Python\Python311\Lib\site-packages\pytest_asyncio\plugin.py:299: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset. +The event loop scope for asynchronous fixtures will default to the "fixture" caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to "function" scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session" + + warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET)) +........................................................................ [ 71%] +............................. [100%] +101 passed in 1.96s diff --git a/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h b/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h index 66af0b89..2e3cc7ce 100644 --- a/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h +++ b/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h @@ -33,6 +33,8 @@ class MediaTransition { void preload_tick(); // video thread only; never starts playback or audio obs_source_t *prepare(bool preview, uint32_t &duration_ms, std::string &error); void finish(); + obs_source_t *playback_source() const { return media(); } // borrowed, lane mutex held + uint32_t cut_point_ms() const { return config_.cut_point_ms; } private: obs_source_t *media() const; diff --git a/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp b/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp index 0d03344a..daee83a4 100644 --- a/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp +++ b/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp @@ -2941,6 +2941,13 @@ class PulsarFrontendAPI : public obs_frontend_callbacks { json switchLaneScene(const json &request); json abortLaneSwitch(const std::string &commandId); json laneSwitchResult(const std::string &commandId); + json beginPreviewMedia(const std::string &commandId); + json endPreviewMedia(const std::string &commandId, bool abort); + json previewMediaResult(const std::string &commandId); + void previewMediaTickLocked(); + void clearPreviewMediaLocked(); + static void OnPreviewMediaStarted(void *param, uint64_t frameId, uint64_t ptsNs); + static void OnPreviewMediaRestored(void *param, uint64_t frameId, uint64_t ptsNs); static void OnLaneSwitchCommitted(void *param, uint64_t frameId, uint64_t ptsNs); static void OnSceneSwitchPreviewVideoFrame(void *param, struct video_data *frame); static void OnDualLaneTick(void *param, float seconds); @@ -3905,6 +3912,13 @@ class PulsarFrontendAPI : public obs_frontend_callbacks { struct LaneOutcome { json request; json result; }; std::map laneOutcomes; json lastLaneSwitch = nullptr; + obs_scene_t *previewMediaOverlay = nullptr; + obs_source_t *previewMediaBase = nullptr; + json previewMediaOutcome = nullptr; + uint64_t previewMediaDeadlineNs = 0; + bool previewMediaReleased = false; + bool previewMediaAborted = false; + bool previewMediaRestoreQueued = false; std::atomic recordingPaused{false}; std::string lastRecording; std::string lastReplay; @@ -4384,6 +4398,9 @@ class PulsarMediaTransitionVendor { obs_websocket_vendor_register_request(vendor_, "GetState", &GetState, this) && obs_websocket_vendor_register_request(vendor_, "SwitchLane", &SwitchLane, this) && obs_websocket_vendor_register_request(vendor_, "GetResult", &GetResult, this) && + obs_websocket_vendor_register_request(vendor_, "BeginPreview", &BeginPreview, this) && + obs_websocket_vendor_register_request(vendor_, "EndPreview", &EndPreview, this) && + obs_websocket_vendor_register_request(vendor_, "GetPreviewResult", &GetPreviewResult, this) && obs_websocket_vendor_register_request(vendor_, "Abort", &Abort, this); return running_; } @@ -4403,6 +4420,9 @@ class PulsarMediaTransitionVendor { static void SwitchLane(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("SwitchLane", a, b); } static void GetResult(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("GetResult", a, b); } static void Abort(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("Abort", a, b); } + static void BeginPreview(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("BeginPreview", a, b); } + static void EndPreview(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("EndPreview", a, b); } + static void GetPreviewResult(obs_data_t *a, obs_data_t *b, void *p) { static_cast(p)->dispatch("GetPreviewResult", a, b); } void dispatch(const std::string &operation, obs_data_t *request, obs_data_t *response) { std::lock_guard lock(mutex_); @@ -4422,6 +4442,14 @@ class PulsarMediaTransitionVendor { out = g_api->configureMediaTransition(value["config"]); else if (operation == "Clear" && value.size() == 1) out = g_api->configureMediaTransition(nullptr); else if (operation == "SwitchLane") out = g_api->switchLaneScene(value); + else if ((operation == "BeginPreview" || operation == "GetPreviewResult") && value.size() == 2 && + value.contains("command_id") && value["command_id"].is_string()) + out = operation == "BeginPreview" ? g_api->beginPreviewMedia(value["command_id"]) : + g_api->previewMediaResult(value["command_id"]); + else if (operation == "EndPreview" && value.size() == 3 && + value.contains("command_id") && value["command_id"].is_string() && + value.contains("abort") && value["abort"].is_boolean()) + out = g_api->endPreviewMedia(value["command_id"], value["abort"]); else if ((operation == "Abort" || operation == "GetResult") && value.size() == 2 && value.contains("command_id") && value["command_id"].is_string()) out = operation == "Abort" ? g_api->abortLaneSwitch(value["command_id"]) : @@ -4445,7 +4473,8 @@ json PulsarFrontendAPI::mediaTransitionState() const char *id = std::getenv("PULSAR_RUNTIME_INSTANCE_ID"); state["runtime_instance_id"] = id && *id ? id : "pulsar-runtime"; state["operational"] = dualLaneReady && dualLaneOperational; - state["busy"] = dualLaneCutPending.load(); + state["busy"] = dualLaneCutPending.load() || previewMediaOverlay != nullptr; + state["preview_overlay"] = previewMediaOutcome.is_null() ? json::object() : previewMediaOutcome; state["role_map"] = {{"on_air", onAirLane == 0 ? "A" : "B"}, {"preview", previewLane == 0 ? "A" : "B"}}; state["last_switch"] = lastLaneSwitch.is_null() ? json::object() : lastLaneSwitch; return state; @@ -4456,13 +4485,145 @@ json PulsarFrontendAPI::configureMediaTransition(const json &config) { std::lock_guard lock(dualLaneMutex); if (!dualLaneReady || !dualLaneOperational) return {{"error", "RUNTIME_UNAVAILABLE"}}; - if (dualLaneCutPending.load()) return {{"error", "TRANSITION_BUSY"}}; + if (dualLaneCutPending.load() || previewMediaOverlay) return {{"error", "TRANSITION_BUSY"}}; std::string error; if (!mediaTransition.configure(config, error)) return {{"error", error}}; } return mediaTransitionState(); } +// The persistent cockpit wire updates one composite in place. Overlay the +// actual PreviewView, hold a decoded cover frame, then reopen after the host +// has admitted Orion and reconciled native sources. Program is never rebound +// to this overlay and its audio never receives the preview media child. +json PulsarFrontendAPI::beginPreviewMedia(const std::string &commandId) +{ + std::lock_guard lock(dualLaneMutex); + if (commandId.empty() || commandId.size() > 128 || commandId.find_first_not_of( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._:-") != std::string::npos) + return {{"error", "REQUEST_INVALID"}}; + if (!previewMediaOutcome.is_null() && previewMediaOutcome["command_id"] == commandId) + return previewMediaOutcome; + if (!dualLaneReady || !dualLaneOperational) return {{"error", "RUNTIME_UNAVAILABLE"}}; + if (dualLaneCutPending.load() || previewMediaOverlay || !sceneSwitchPreparedCommandId.empty()) + return {{"error", "TRANSITION_BUSY"}}; + OBSSourceAutoRelease base = obs_view_get_source(previewView, 0); + if (!base || std::strcmp(obs_source_get_name(base), "ZabPreviewComposite") != 0) + return {{"error", "PREVIEW_COMPOSITE_REQUIRED"}}; + uint32_t duration = 0; + std::string error; + if (!mediaTransition.prepare(true, duration, error)) return {{"error", error}}; + auto *media = mediaTransition.playback_source(); + previewMediaOverlay = obs_scene_create_private("PulsarPreviewMediaOverlay"); + if (!previewMediaOverlay) return {{"error", "PREVIEW_OVERLAY_UNAVAILABLE"}}; + previewMediaBase = obs_source_get_ref(base); + obs_scene_add(previewMediaOverlay, base); + auto *item = obs_scene_add(previewMediaOverlay, media); + if (!item) { + clearPreviewMediaLocked(); + return {{"error", "PREVIEW_OVERLAY_UNAVAILABLE"}}; + } + obs_video_info video = {}; + obs_get_video_info(&video); + vec2 bounds; + vec2_set(&bounds, static_cast(video.base_width), static_cast(video.base_height)); + obs_sceneitem_set_bounds_type(item, OBS_BOUNDS_STRETCH); + obs_sceneitem_set_bounds(item, &bounds); + obs_sceneitem_set_bounds_alignment(item, OBS_ALIGN_TOP | OBS_ALIGN_LEFT); + previewMediaReleased = previewMediaAborted = previewMediaRestoreQueued = false; + previewMediaDeadlineNs = os_gettime_ns() + (uint64_t{duration} + 20000) * 1000000ULL; + const char *runtime = std::getenv("PULSAR_RUNTIME_INSTANCE_ID"); + previewMediaOutcome = {{"command_id", commandId}, {"runtime_instance_id", runtime ? runtime : "pulsar-runtime"}, + {"operational", true}, {"channel", "preview"}, {"status", "starting"}}; + OBSSourceAutoRelease program = obs_view_get_source(programView, 0); + if (!obs_view_queue_atomic_swap_with_floor(programView, 0, program, previewView, 0, + obs_scene_get_source(previewMediaOverlay), os_gettime_ns(), OnPreviewMediaStarted, this)) { + clearPreviewMediaLocked(); + previewMediaOutcome["status"] = "failed"; + return {{"error", "ATOMIC_SWAP_REJECTED"}}; + } + return previewMediaOutcome; +} + +void PulsarFrontendAPI::OnPreviewMediaStarted(void *param, uint64_t frameId, uint64_t ptsNs) +{ + auto *self = static_cast(param); + std::lock_guard lock(self->dualLaneMutex); + if (!self->previewMediaOverlay) return; + obs_source_media_play_pause(self->mediaTransition.playback_source(), false); + obs_source_media_restart(self->mediaTransition.playback_source()); + self->previewMediaOutcome["status"] = "closing"; + self->previewMediaOutcome["start_frame_id"] = frameId; + self->previewMediaOutcome["start_pts_ns"] = ptsNs; +} + +json PulsarFrontendAPI::previewMediaResult(const std::string &commandId) +{ + std::lock_guard lock(dualLaneMutex); + return !previewMediaOutcome.is_null() && previewMediaOutcome["command_id"] == commandId ? + previewMediaOutcome : json{{"error", "COMMAND_NOT_FOUND"}}; +} + +json PulsarFrontendAPI::endPreviewMedia(const std::string &commandId, bool abort) +{ + std::lock_guard lock(dualLaneMutex); + if (previewMediaOutcome.is_null() || previewMediaOutcome["command_id"] != commandId) + return {{"error", "COMMAND_NOT_FOUND"}}; + if (!previewMediaOverlay) return previewMediaOutcome; + if (!abort && previewMediaOutcome["status"] != "covered" && !previewMediaReleased) + return {{"error", "PREVIEW_NOT_COVERED"}}; + previewMediaReleased = true; + previewMediaAborted = previewMediaAborted || abort; + if (!abort) { + obs_source_media_play_pause(mediaTransition.playback_source(), false); + previewMediaOutcome["status"] = "opening"; + } + return previewMediaOutcome; +} + +void PulsarFrontendAPI::clearPreviewMediaLocked() +{ + if (previewMediaOverlay) obs_scene_release(previewMediaOverlay); + previewMediaOverlay = nullptr; + if (previewMediaBase) obs_source_release(previewMediaBase); + previewMediaBase = nullptr; + if (mediaTransition.playback_source()) obs_source_media_stop(mediaTransition.playback_source()); + mediaTransition.finish(); +} + +void PulsarFrontendAPI::OnPreviewMediaRestored(void *param, uint64_t frameId, uint64_t ptsNs) +{ + auto *self = static_cast(param); + std::lock_guard lock(self->dualLaneMutex); + if (!self->previewMediaOverlay) return; + self->previewMediaOutcome["status"] = self->previewMediaAborted ? "aborted" : "completed"; + self->previewMediaOutcome["frame_id"] = frameId; + self->previewMediaOutcome["pts_ns"] = ptsNs; + self->clearPreviewMediaLocked(); +} + +void PulsarFrontendAPI::previewMediaTickLocked() +{ + if (!previewMediaOverlay || previewMediaRestoreQueued) return; + auto *media = mediaTransition.playback_source(); + if (os_gettime_ns() >= previewMediaDeadlineNs || obs_source_media_get_state(media) == OBS_MEDIA_STATE_ERROR) { + previewMediaAborted = true; + previewMediaOutcome["failure_reason"] = "PREVIEW_TRANSITION_TIMEOUT_OR_DECODE_ERROR"; + } + if (!previewMediaReleased && previewMediaOutcome["status"] == "closing" && + obs_source_media_get_time(media) >= mediaTransition.cut_point_ms()) { + obs_source_media_play_pause(media, true); + previewMediaOutcome["status"] = "covered"; + previewMediaOutcome["cut_time_ms"] = obs_source_media_get_time(media); + } + const auto state = obs_source_media_get_state(media); + if (!previewMediaAborted && (!previewMediaReleased || + (state != OBS_MEDIA_STATE_ENDED && state != OBS_MEDIA_STATE_STOPPED))) return; + OBSSourceAutoRelease program = obs_view_get_source(programView, 0); + previewMediaRestoreQueued = obs_view_queue_atomic_swap_with_floor(programView, 0, program, + previewView, 0, previewMediaBase, os_gettime_ns(), OnPreviewMediaRestored, this); +} + json PulsarFrontendAPI::laneSwitchResult(const std::string &commandId) { std::lock_guard lock(dualLaneMutex); @@ -4492,7 +4653,7 @@ json PulsarFrontendAPI::switchLaneScene(const json &request) if (existing != laneOutcomes.end()) return existing->second.request == request ? existing->second.result : json{{"error", "IDEMPOTENCY_CONFLICT"}}; if (!dualLaneReady || !dualLaneOperational) return {{"error", "RUNTIME_UNAVAILABLE"}}; - if (dualLaneCutPending.load() || !sceneSwitchPreparedCommandId.empty()) return {{"error", "TRANSITION_BUSY"}}; + if (dualLaneCutPending.load() || previewMediaOverlay || !sceneSwitchPreparedCommandId.empty()) return {{"error", "TRANSITION_BUSY"}}; if (laneOutcomes.size() >= 1024) return {{"error", "COMMAND_CAPACITY_REACHED"}}; const int lane = laneName == "A" ? 0 : 1; obs_source_t *outgoing = lane == onAirLane ? programSelection : previewSelection; @@ -4864,6 +5025,12 @@ bool PulsarFrontendAPI::setPreviewCompositeSource(bool enabled) } } + // Native source reconciliation may re-acknowledge this binding while the + // transition cover is held. Preserve the overlay until its frame callback. + if (previewMediaOverlay) { + if (enabled) obs_source_release(source); + return enabled; + } obs_view_set_source(previewView, 0, source); if (enabled) obs_source_release(source); @@ -5198,7 +5365,7 @@ bool PulsarFrontendAPI::sceneSwitchPrepare(const std::string &commandId, char la "[pulsar-dual-lane] Prepare rejected: rollback freeze is active"); } const char expectedLane = previewLane == 0 ? 'A' : 'B'; - if (dualLaneReady && dualLaneOperational && !dualLaneCutPending.load() && laneId == expectedLane && + if (dualLaneReady && dualLaneOperational && !dualLaneCutPending.load() && !previewMediaOverlay && laneId == expectedLane && scene != programSelection && scene != currentScene && obs_scene_from_source(scene)) { // Hold the previous public selection before the physical child is // replaced. A postcondition failure must be able to restore both @@ -5408,6 +5575,7 @@ void PulsarFrontendAPI::OnDualLaneTransitionAbortCommitted(void *param, uint64_t void PulsarFrontendAPI::dualLaneTransitionTick() { std::lock_guard lock(dualLaneMutex); + if (previewMediaOverlay) { previewMediaTickLocked(); return; } if (!dualLaneCutPending.load()) mediaTransition.preload_tick(); if (sameLaneTarget >= 0 && sameLaneAborted) { if (!dualLaneTransitionFinalPending) { @@ -5474,6 +5642,10 @@ bool PulsarFrontendAPI::queueDualLaneCut(obs_source_t *scene) uint32_t requestedDuration = static_cast((std::max)(0, transitionDuration)); { std::lock_guard lk(dualLaneMutex); + if (previewMediaOverlay) { + g_runtimeTelemetry.cancelPending(); + return false; + } if (!dualLaneReady || !dualLaneOperational || !scene || !dualLaneInvariantLocked("queue-before")) { g_runtimeTelemetry.cancelPending(); if (dualLaneReady && !dualLaneOperational) @@ -6762,6 +6934,7 @@ void PulsarFrontendAPI::teardown() dualLaneCutPending.store(false); } obs_view_cancel_atomic_swap(); + clearPreviewMediaLocked(); mediaTransition.clear(); if (sameLaneScene) { obs_source_release(sameLaneScene); diff --git a/plugins/pulsar-websocket/src/requesthandler/RequestHandler.cpp b/plugins/pulsar-websocket/src/requesthandler/RequestHandler.cpp index 2e5a08f1..f02a8140 100644 --- a/plugins/pulsar-websocket/src/requesthandler/RequestHandler.cpp +++ b/plugins/pulsar-websocket/src/requesthandler/RequestHandler.cpp @@ -84,7 +84,7 @@ bool IsControlledSceneSwitchPendingBypass(const Request &request) return false; const std::string nested = nestedRequest->get(); if (vendor->get() == "pulsar-transitions") - return nested == "Abort" || nested == "GetState" || nested == "GetResult"; + return nested == "Abort" || nested == "GetState" || nested == "GetResult" || nested == "GetPreviewResult"; return vendor->get() == "pulsar-scene-switch" && (nested == "Abort" || nested == "GetState"); } diff --git a/scripts/contracts/scene_control/test_dual_lane_contract.py b/scripts/contracts/scene_control/test_dual_lane_contract.py index be1b1fa0..848353ce 100644 --- a/scripts/contracts/scene_control/test_dual_lane_contract.py +++ b/scripts/contracts/scene_control/test_dual_lane_contract.py @@ -679,7 +679,7 @@ def test_websocket_mutation_gate_is_central_and_fail_closed() -> None: assert 'request.RequestType != "CallVendorRequest"' in handler assert 'vendor->get() == "pulsar-scene-switch" && (nested == "Abort" || nested == "GetState")' in handler assert 'if (vendor->get() == "pulsar-transitions")' in handler - assert 'return nested == "Abort" || nested == "GetState" || nested == "GetResult";' in handler + assert 'return nested == "Abort" || nested == "GetState" || nested == "GetResult" || nested == "GetPreviewResult";' in handler assert "vendor->is_string() || !nestedRequest->is_string()" in handler assert "json::value() here" in handler assert "const bool controlledSceneSwitchBypass" in handler From 3e77323d1915e7ff09cc799594474ed56d758c2c Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Thu, 8 Oct 2026 23:00:20 +0200 Subject: [PATCH 06/17] fix(preview): play media transitions continuously after scene preparation Capture the outgoing composite while the host stages the incoming scene, then play the configured media without pausing at the cut. Isolate each Preview decoder from late stop callbacks and retain native restoration frame evidence. Agent-Role: eleven Agent-Thread: 01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7 Work-Unit: local-20261008-webm-transition Issue: local-20261008-webm-transition Evidence: evidence/local-20261008-webm-transition/eleven/20261008T205800Z-continuous-report.md --- docs/contracts/configurable-transitions.md | 37 +- .../20261008T203600Z-continuous-plan.md | 17 + ...20261008T204015Z-native-first-failure.json | 607 +++++ ...0261008T204136Z-native-replay-failure.json | 641 +++++ .../20261008T204416Z-continuous-native.json | 2413 +++++++++++++++++ .../eleven/20261008T204716Z-ctest.txt | 18 + .../20261008T204747Z-scene-contracts.txt | 7 + .../20261008T204800Z-native-regressions.json | 735 +++++ ...008T204920Z-engine-activation-attempt.json | 23 + .../20261008T205739Z-active-runtime.json | 77 + .../20261008T205800Z-continuous-report.md | 87 + plugins/pulsar-frontend-stub/CMakeLists.txt | 1 + plugins/pulsar-frontend-stub/README.md | 10 + .../include/pulsar-media-transition.h | 2 + .../include/pulsar-preview-frame.h | 10 + .../src/pulsar-frontend-stub.cpp | 105 +- .../src/pulsar-media-transition.cpp | 25 +- .../src/pulsar-preview-frame.cpp | 112 + scripts/probe-preview-transition.py | 171 ++ 19 files changed, 5065 insertions(+), 33 deletions(-) create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T203600Z-continuous-plan.md create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T204015Z-native-first-failure.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T204136Z-native-replay-failure.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T204416Z-continuous-native.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T204716Z-ctest.txt create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T204747Z-scene-contracts.txt create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T204800Z-native-regressions.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T204920Z-engine-activation-attempt.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T205739Z-active-runtime.json create mode 100644 evidence/local-20261008-webm-transition/eleven/20261008T205800Z-continuous-report.md create mode 100644 plugins/pulsar-frontend-stub/include/pulsar-preview-frame.h create mode 100644 plugins/pulsar-frontend-stub/src/pulsar-preview-frame.cpp create mode 100644 scripts/probe-preview-transition.py diff --git a/docs/contracts/configurable-transitions.md b/docs/contracts/configurable-transitions.md index 0454fc9b..92a36bf9 100644 --- a/docs/contracts/configurable-transitions.md +++ b/docs/contracts/configurable-transitions.md @@ -150,24 +150,32 @@ are the transition operations allowed through the pending-mutation gate. Prism's cockpit rail updates one `ZabPreviewComposite` in place. It uses `BeginPreview` with `{runtime_instance_id, command_id}` rather than staging a -second OBS scene. Pulsar creates a private scene containing the bound composite -and the configured decoder child, and attaches it only to PreviewView at an +second OBS scene. Pulsar creates a private scene containing a GPU snapshot of +the outgoing composite, the live composite, and a private Preview decoder +derived from the configured media. It attaches this scene only to PreviewView at an atomic frame boundary. Program's source, output and audio remain independent; the Preview media child is muted. Beginning requires a ready configured decoder and rejects concurrent Take, same-lane transition or prepared Take. Poll `GetPreviewResult` with the same runtime/command IDs. The states are -`starting`, `closing`, then `covered`: Pulsar pauses the media when its clock -reaches the configured cut point, within one video tick. The result reports -`cut_time_ms`. The host may now change Orion's persistent Preview wire and -reconcile native sources while the cover remains rendered. Opacity at this -point remains the media author's responsibility. `SetPreviewComposite` +`starting`, `preparing`, then `covered`. The latter means that the outgoing +frame has actually been captured and the Preview decoder has a decoded first +frame (`cover_kind: "outgoing_frame"). Playback has not started. The host may +now change Orion's persistent Preview wire and reconcile native sources behind +this captured frame. The snapshot's active child keeps the live composite and +its producers hot; one canvas-sized GPU texture is held, with no CPU copy or +frame queue. `SetPreviewComposite` acknowledges the existing composite without removing an active cover. Call `EndPreview` with `{runtime_instance_id, command_id, abort: false}` once -the incoming scene is ready. It unpauses the media (`opening`) and restores the -original composite when playback ends. `completed` contains the actual -restoration `frame_id`/`pts_ns`; the start frame is also reported. An early +the incoming scene is ready. This starts uninterrupted media playback at a +native frame boundary (`closing`). When the media clock reaches the configured +cut point, Pulsar shows the live incoming composite and releases the snapshot +item (`opening`), without pausing or seeking the video. Opacity at the cut +remains the media author's responsibility. `media_time_ms`, `media_paused`, and +`cut_time_ms` expose playback continuity. Pulsar restores the original composite +when playback ends. `completed` contains the actual restoration `frame_id`/`pts_ns`; +capture and playback-start frames/PTS are reported separately. An early failure or superseded host request uses `abort: true` and awaits `aborted` at the restoration frame. Normal release before `covered` is refused. Configure, Clear and other transitions remain busy throughout this operation; Preview @@ -176,6 +184,15 @@ or decoding fails, Pulsar restores the view itself within the configured media duration plus 20 seconds. The most recent Preview outcome is retained for readback and command replay, separate from `SwitchLane` outcomes. +The Preview decoder is owned only by this operation and released on restore, +abort or failure. Late stop callbacks from a previous operation or the Take +stinger cannot interrupt a subsequent Preview animation. The existing host +Begin/prepare/End sequencing remains valid; preparation latency occurs before +the video begins instead of stopping it at the midpoint. The isolated +`scripts/probe-preview-transition.py` checks delayed preparation, continuous +media time across the cut, immediate replay, two abort phases and graceful +shutdown using synthetic scenes without recording or streaming. + Vendor validation failures return `{ "error": "CODE" }`; transport admission failures use the existing obs-websocket error envelope. Stable codes include `REQUEST_INVALID`, `RUNTIME_MISMATCH`, `RUNTIME_UNAVAILABLE`, `CONFIG_INVALID`, diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T203600Z-continuous-plan.md b/evidence/local-20261008-webm-transition/eleven/20261008T203600Z-continuous-plan.md new file mode 100644 index 00000000..1e6a25c0 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T203600Z-continuous-plan.md @@ -0,0 +1,17 @@ +# Continuous Preview playback correction + +Authority: user reports the overlay freezing during scene switches. Continue the existing transition branch, no ADR, Computer Use, default asset or live/recording. Preserve the scene-sync checkout and active host, which currently includes our existing Begin/End IPC. Native Begin/End wire shape and covered acknowledgment remain compatible. + +Cause observed in source and prior native/renderer traces: the media is intentionally paused at 875 ms while Orion and native capture reconciliation run; this adds a visible stop in an authored animation. Native pause is not a stalled DirectShow queue. + +Use one bounded GPU capture of the outgoing composite as the preparation cover. Keep the real composite hot behind it. Once the host's existing End(false) signals incoming readiness, start the selected media once at a frame boundary, reveal the live composite at the configured cut, and complete without pausing the media. End(true), decode error and timeout still restore the real Preview. + +| Criterion | Risk | Validation | Evidence | +|---|---|---|---| +| Overlay video plays continuously | Accidental decoded-frame hold | Native clock/pause state and live sampled video over the cut | Isolated synthetic probe and real Preview trace | +| Scene preparation finishes before animation starts | Early incoming decor exposure | Delay preparation and change base while captured | Outgoing pixels retained until cut, incoming pixels after | +| Real source stays hot | Freeze source deactivates camera/CEF | Active source/tree and replay/cleanup | Native repeated preparation/playback | +| Program/Take unaffected | View or audio contamination | Existing contracts and Program source identity | Native checks plus actual idle host | +| No leaked texture/lease | Abort or timeout retains owned frame | Abort before playback, abort in motion, repeat | Terminal frame IDs and busy=false | + +Reuse the existing OBS texrender/effect render path for a private frozen-frame source; do not add files/assets to Prism or replace the renderer's real native return. Snapshot is not media stored on disk. Only the frontend owns this source, and it is released with the transition. diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T204015Z-native-first-failure.json b/evidence/local-20261008-webm-transition/eleven/20261008T204015Z-native-first-failure.json new file mode 100644 index 00000000..9c035ce3 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T204015Z-native-first-failure.json @@ -0,0 +1,607 @@ +{ + "passed": false, + "exe_sha256": "972d8a3d4b4293325e00798d8259351870b2b096504e3930905b8c5728602e9d", + "runtime_instance_id": "runtime-25059e5d61cc1371", + "runs": [ + { + "preparation_delay_ms": 800.0, + "capture_ms": 61.55909993685782, + "cut_window_wall_ms": 421.88859987072647, + "cut_window_media_ms": 400, + "samples": [ + { + "elapsed_ms": 1.1457998771220446, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "starting" + }, + { + "elapsed_ms": 45.16329988837242, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_time_ms": 0, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 92.55469986237586, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 139.18689987622201, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 66, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 185.80639990977943, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 66, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 232.6693998184055, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 278.7064998410642, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 134, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 325.9141000453383, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 373.20559984073043, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 234, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 420.2469999436289, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 266, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 467.59209991432726, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 515.8555000089109, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 560.6688000261784, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 607.2199000045657, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 653.8615999743342, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 533, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 701.7965998966247, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 749.3622999172658, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 794.7708999272436, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 634, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "closing" + }, + { + "elapsed_ms": 842.1870998572558, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 889.1594999004155, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 766, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 935.3681998327374, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 983.0437998753041, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 834, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1029.1084998752922, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1075.9300999343395, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 934, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1122.961799846962, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1169.800200033933, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1034, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1216.7656999081373, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1266.201299848035, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1311.5027998574078, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1166, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1358.8963998481631, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1233, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1405.0393998622894, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1266, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1452.6972998864949, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1500.0922998879105, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1366, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1545.5699998419732, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1592.8346998989582, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1434, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1639.2208999022841, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1685.3175000287592, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "opening" + }, + { + "elapsed_ms": 1733.3150999620557, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 169, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 119, + "playback_start_pts_ns": 1696566851790294, + "pts_ns": 1696568518456944, + "runtime_instance_id": "runtime-25059e5d61cc1371", + "start_frame_id": 92, + "start_pts_ns": 1696565951790303, + "status": "completed" + } + ] + } + ], + "failure": "AssertionError({'closing', 'completed', 'starting'})", + "shutdown": { + "exit_code": 0, + "forced": false + } +} diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T204136Z-native-replay-failure.json b/evidence/local-20261008-webm-transition/eleven/20261008T204136Z-native-replay-failure.json new file mode 100644 index 00000000..db3ea2ea --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T204136Z-native-replay-failure.json @@ -0,0 +1,641 @@ +{ + "passed": false, + "exe_sha256": "972d8a3d4b4293325e00798d8259351870b2b096504e3930905b8c5728602e9d", + "runtime_instance_id": "runtime-93de03f133145d4c", + "runs": [ + { + "preparation_delay_ms": 800.0, + "capture_ms": 93.5477998573333, + "cut_window_wall_ms": 421.030999859795, + "cut_window_media_ms": 400, + "samples": [ + { + "elapsed_ms": 1.195400021970272, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "starting" + }, + { + "elapsed_ms": 46.24820011667907, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 93.75840006396174, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 141.0711999051273, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 188.95290000364184, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 234.95810013264418, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 166, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 282.6435000170022, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 330.09709999896586, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 376.1237000580877, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 424.2271000985056, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 333, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 469.2091001197696, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 516.311899991706, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 564.2095999792218, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 434, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 610.1311000529677, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 656.920500099659, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 566, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 704.8150999471545, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 751.6129000578076, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 666, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "closing" + }, + { + "elapsed_ms": 798.7842001020908, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 845.132099930197, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 734, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 890.8514999784529, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 938.3968999609351, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 834, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 985.136799979955, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1031.1620999127626, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1078.1606000382453, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1000, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1124.363699927926, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1000, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1172.8566999081522, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1220.3025999478996, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1266.4747000671923, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1134, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1313.5085999965668, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1361.2844999879599, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1266, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1408.608099911362, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1454.1303000878543, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1334, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1500.6031000521034, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1548.5795000568032, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1434, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1594.8433000594378, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1640.65539999865, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "opening" + }, + { + "elapsed_ms": 1689.667999977246, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 108, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 58, + "playback_start_pts_ns": 1696647063414781, + "pts_ns": 1696648730081431, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 31, + "start_pts_ns": 1696646163414790, + "status": "completed" + } + ] + } + ], + "last_attempt": { + "preparation_delay_ms": 0, + "samples": [ + { + "elapsed_ms": 0.9266000706702471, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 112, + "start_pts_ns": 1696648863414763, + "status": "starting" + }, + { + "elapsed_ms": 59.43150003440678, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 115, + "playback_start_pts_ns": 1696648963414762, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 112, + "start_pts_ns": 1696648863414763, + "status": "closing" + }, + { + "elapsed_ms": 107.5333000626415, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "frame_id": 117, + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 115, + "playback_start_pts_ns": 1696648963414762, + "pts_ns": 1696649030081428, + "runtime_instance_id": "runtime-93de03f133145d4c", + "start_frame_id": 112, + "start_pts_ns": 1696648863414763, + "status": "completed" + } + ] + }, + "failure": "AssertionError({'completed', 'starting', 'closing'})", + "shutdown": { + "exit_code": 0, + "forced": false + } +} diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T204416Z-continuous-native.json b/evidence/local-20261008-webm-transition/eleven/20261008T204416Z-continuous-native.json new file mode 100644 index 00000000..fdb9f837 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T204416Z-continuous-native.json @@ -0,0 +1,2413 @@ +{ + "passed": true, + "exe_sha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "runs": [ + { + "preparation_delay_ms": 800.0, + "capture_ms": 62.71800003014505, + "cut_window_wall_ms": 375.23850006982684, + "cut_window_media_ms": 366, + "samples": [ + { + "elapsed_ms": 0.7935999892652035, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "starting" + }, + { + "elapsed_ms": 46.642599860206246, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 92.347800033167, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 66, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 139.80329991318285, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 66, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 187.18619993887842, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 133, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 234.27469981834292, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 166, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 282.24819991737604, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 233, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 327.4991998914629, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 266, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 373.8551998976618, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 421.6034999117255, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 467.9234998766333, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 515.8540999982506, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 466, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 560.6314998585731, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 608.0052999313921, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 533, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 656.0765998438001, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 701.9259999506176, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 633, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "closing" + }, + { + "elapsed_ms": 749.1868999786675, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 797.5011998787522, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 840.7127999234945, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 734, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 888.2321999408305, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 833, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 935.8699999283999, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 866, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 983.5816998966038, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 933, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1031.842599855736, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1075.9596999268979, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1122.394499834627, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1066, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1169.836999848485, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1217.0235000085086, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1166, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1265.6570998951793, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1310.3318000212312, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1233, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1356.1018998734653, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1402.63170003891, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1333, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1449.6011999435723, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1496.7861999757588, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1544.0284998621792, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1466, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1590.4969000257552, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1637.0910999830812, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "opening" + }, + { + "elapsed_ms": 1684.6145000308752, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 100, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 51, + "playback_start_pts_ns": 1696828032521950, + "pts_ns": 1696829665855267, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 25, + "start_pts_ns": 1696827165855292, + "status": "completed" + } + ] + }, + { + "preparation_delay_ms": 0, + "capture_ms": 62.798399943858385, + "cut_window_wall_ms": 378.0787999276072, + "cut_window_media_ms": 400, + "samples": [ + { + "elapsed_ms": 0.8425998967140913, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "starting" + }, + { + "elapsed_ms": 28.90020003542304, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_time_ms": 0, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 73.5704000107944, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 120.5783998593688, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 66, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 166.7022998444736, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 66, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 214.4094998948276, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 261.0923999454826, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 134, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 308.02490003407, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 354.3729998636991, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 400.82790004089475, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 448.499099817127, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 496.50130001828074, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 541.9432998169214, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 589.8716000374407, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 466, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 636.8835000321269, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 683.7536999955773, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 534, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 731.7802000325173, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 633, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 779.8673999495804, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 634, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "closing" + }, + { + "elapsed_ms": 825.1292000059038, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 872.18809989281, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 766, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 919.7303999681026, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 968.3629998471588, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 866, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1014.9622999597341, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1062.8269999288023, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1109.2141999397427, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1000, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1155.6611999403685, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1000, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1204.3540999293327, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1250.6995999719948, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1297.416599933058, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1343.8368998467922, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1391.9153998140246, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1266, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1439.3787998706102, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1333, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1486.430199816823, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1366, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1533.9142999146134, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1581.3533999025822, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1466, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1631.1671000439674, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1677.566499914974, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "opening" + }, + { + "elapsed_ms": 1723.433299921453, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 154, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 104, + "playback_start_pts_ns": 1696829799188599, + "pts_ns": 1696831465855249, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 102, + "start_pts_ns": 1696829732521933, + "status": "completed" + } + ] + }, + { + "preparation_delay_ms": 350.0, + "capture_ms": 110.62980000860989, + "cut_window_wall_ms": 423.33159991540015, + "cut_window_media_ms": 400, + "samples": [ + { + "elapsed_ms": 1.505000051110983, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "starting" + }, + { + "elapsed_ms": 44.582700124010444, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_time_ms": 0, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 91.87839995138347, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 140.71490010246634, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 187.22690013237298, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 234.74560002796352, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 282.6249001082033, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 166, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 327.30409991927445, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 374.65270003303885, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 266, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 421.9967999961227, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 468.66229991428554, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 515.8772000577301, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 561.6242000833154, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 608.8982000946999, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 655.261900043115, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 702.1904001012444, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 749.4529001414776, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 633, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 796.7930999584496, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 842.8128999657929, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 733, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 890.2867001015693, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 766, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 936.3093001302332, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 983.2834000699222, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 866, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1032.2298000101, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1078.2105000689626, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1124.4471000973135, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1000, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1170.6431999336928, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1066, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1216.9695999473333, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1265.46539994888, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1166, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1312.033100053668, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1166, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1358.9641000144184, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1233, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1406.0881999321282, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1452.2023999597877, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1499.2817000020295, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1545.7512000575662, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1593.016100116074, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1466, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1640.8261000178754, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1687.2187999542803, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1733.7957001291215, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 220, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "pts_ns": 1696833665855227, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "completed" + } + ] + } + ], + "last_attempt": { + "preparation_delay_ms": 350.0, + "samples": [ + { + "elapsed_ms": 1.505000051110983, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "starting" + }, + { + "elapsed_ms": 44.582700124010444, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_time_ms": 0, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 91.87839995138347, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 140.71490010246634, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 187.22690013237298, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 234.74560002796352, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 282.6249001082033, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 166, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 327.30409991927445, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 374.65270003303885, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 266, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 421.9967999961227, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 468.66229991428554, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 515.8772000577301, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 561.6242000833154, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 608.8982000946999, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 655.261900043115, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 702.1904001012444, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 749.4529001414776, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 633, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "closing" + }, + { + "elapsed_ms": 796.7930999584496, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 842.8128999657929, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 733, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 890.2867001015693, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 766, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 936.3093001302332, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 983.2834000699222, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 866, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1032.2298000101, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1078.2105000689626, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1124.4471000973135, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1000, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1170.6431999336928, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1066, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1216.9695999473333, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1265.46539994888, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1166, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1312.033100053668, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1166, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1358.9641000144184, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1233, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1406.0881999321282, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1452.2023999597877, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1499.2817000020295, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1545.7512000575662, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1593.016100116074, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1466, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1640.8261000178754, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1687.2187999542803, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "opening" + }, + { + "elapsed_ms": 1733.7957001291215, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 220, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 170, + "playback_start_pts_ns": 1696831999188577, + "pts_ns": 1696833665855227, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 156, + "start_pts_ns": 1696831532521915, + "status": "completed" + } + ] + }, + "aborts": [ + { + "phase": "preparing", + "result": { + "channel": "preview", + "command_id": "abort-preparing", + "cover_kind": "outgoing_frame", + "frame_id": 225, + "operational": true, + "pts_ns": 1696833832521892, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 222, + "start_pts_ns": 1696833732521893, + "status": "aborted" + } + }, + { + "phase": "playing", + "result": { + "channel": "preview", + "command_id": "abort-playing", + "cover_kind": "outgoing_frame", + "frame_id": 243, + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 230, + "playback_start_pts_ns": 1696833999188557, + "pts_ns": 1696834432521886, + "runtime_instance_id": "runtime-4e36d05bdbc79f48", + "start_frame_id": 227, + "start_pts_ns": 1696833899188558, + "status": "aborted" + } + } + ], + "shutdown": { + "exit_code": 0, + "forced": false + } +} diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T204716Z-ctest.txt b/evidence/local-20261008-webm-transition/eleven/20261008T204716Z-ctest.txt new file mode 100644 index 00000000..e75761ed --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T204716Z-ctest.txt @@ -0,0 +1,18 @@ +Test project D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261008-webm-transition/build-transition + Start 8: pulsar-rtwq-lifecycle +1/4 Test #8: pulsar-rtwq-lifecycle .................. Passed 0.05 sec + Start 15: pulsar-transition-controller-probe +2/4 Test #15: pulsar-transition-controller-probe ..... Passed 0.05 sec + Start 16: pulsar-media-transition-config-probe +3/4 Test #16: pulsar-media-transition-config-probe ... Passed 0.05 sec + Start 25: pulsar-headless-websocket-quiesce +4/4 Test #25: pulsar-headless-websocket-quiesce ...... Passed 13.63 sec + +100% tests passed, 0 tests failed out of 4 + +Label Time Summary: +integration = 13.68 sec*proc (2 tests) +runtime = 13.68 sec*proc (2 tests) +unit = 0.10 sec*proc (2 tests) + +Total Test time (real) = 14.10 sec diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T204747Z-scene-contracts.txt b/evidence/local-20261008-webm-transition/eleven/20261008T204747Z-scene-contracts.txt new file mode 100644 index 00000000..e22875db --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T204747Z-scene-contracts.txt @@ -0,0 +1,7 @@ +C:\Users\Mathias\AppData\Local\Programs\Python\Python311\Lib\site-packages\pytest_asyncio\plugin.py:299: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset. +The event loop scope for asynchronous fixtures will default to the "fixture" caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to "function" scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session" + + warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET)) +........................................................................ [ 71%] +............................. [100%] +101 passed in 1.74s diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T204800Z-native-regressions.json b/evidence/local-20261008-webm-transition/eleven/20261008T204800Z-native-regressions.json new file mode 100644 index 00000000..9c7ede75 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T204800Z-native-regressions.json @@ -0,0 +1,735 @@ +{ + "passed": true, + "default": { + "busy": false, + "config": {}, + "configured": false, + "last_switch": {}, + "media_duration_ms": 0, + "operational": true, + "preview_overlay": {}, + "readiness_error": "NOT_CONFIGURED", + "ready": false, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "transition_duration_ms": 0 + }, + "ready": { + "busy": false, + "config": { + "cut_point_ms": 700, + "muted": false, + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261008-webm-transition\\build-transition\\continuous-regression-fixtures\\fixture.webm", + "volume": 0.5 + }, + "configured": true, + "last_switch": {}, + "media_duration_ms": 1508, + "operational": true, + "preview_overlay": {}, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "transition_duration_ms": 1758 + }, + "same_program": { + "result": { + "command_id": "same-program", + "frame_id": 92, + "lane_id": "A", + "pts_ns": 1696906859025003, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "green", + "status": "completed" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261008-webm-transition\\build-transition\\continuous-regression-fixtures\\pulsar-20261008-224551.mkv", + "frames": 76, + "audio_rms": 0.04662632942199707, + "centers": [ + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 212.0, + 40.0, + 35.0 + ], + [ + 203.0, + 47.0, + 35.0 + ], + [ + 197.0, + 60.0, + 33.0 + ], + [ + 193.0, + 74.0, + 32.0 + ], + [ + 193.0, + 91.0, + 31.0 + ], + [ + 195.0, + 109.0, + 28.0 + ], + [ + 201.0, + 127.0, + 28.0 + ], + [ + 212.0, + 146.0, + 27.0 + ], + [ + 226.0, + 165.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 223.0, + 173.0, + 27.0 + ], + [ + 196.0, + 169.0, + 27.0 + ], + [ + 171.0, + 169.0, + 28.0 + ], + [ + 145.0, + 173.0, + 30.0 + ], + [ + 119.0, + 179.0, + 31.0 + ], + [ + 96.0, + 186.0, + 32.0 + ], + [ + 74.0, + 194.0, + 33.0 + ], + [ + 53.0, + 203.0, + 35.0 + ], + [ + 41.0, + 212.0, + 37.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ] + ] + }, + "same_preview": { + "result": { + "command_id": "same-preview", + "frame_id": 173, + "lane_id": "B", + "pts_ns": 1696909559024976, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "red", + "status": "completed" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261008-webm-transition\\build-transition\\continuous-regression-fixtures\\pulsar-20261008-224554.mkv", + "audio_rms": 0.0 + }, + "take": { + "state": { + "contract": "pulsar.scene-switch.v1", + "frozen": false, + "idempotency_cache_capacity": 4096, + "idempotency_cache_entries": 2, + "operational": true, + "revisions": { + "preview": 2, + "program": 2, + "role_map": 1 + }, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "schema_version": 1, + "server_seq": 4, + "state": "ready" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261008-webm-transition\\build-transition\\continuous-regression-fixtures\\pulsar-20261008-224557.mkv", + "audio_rms": 0.046554744243621826, + "committed_event": { + "command_id": "test-take-command", + "contract": "pulsar.scene-switch.v1", + "event_type": "TakeCommitted", + "frame_id": 263, + "intent_id": "test-take", + "message_type": "event", + "observed_at_monotonic_ns": 1696912560500000, + "payload_sha256": "c60c24e3154968a7339e737101d0293d251ca02e3adc3543a5ec84db822ce3fc", + "preview_lane_id": "A", + "previous_revisions": { + "preview": 2, + "program": 1, + "role_map": 0 + }, + "previous_role_map": { + "on_air": "A", + "preview": "B" + }, + "program_lane_id": "B", + "pts_ns": 1696912559024946, + "revisions": { + "preview": 2, + "program": 2, + "role_map": 1 + }, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "schema_version": 1, + "server_seq": 4, + "source_lane_id": "A", + "state": "ready", + "take_command_id": "test-take-command", + "target_lane_id": "B", + "target_scene_id": "red" + } + }, + "abort": { + "command_id": "abort-me", + "frame_id": 300, + "lane_id": "B", + "pts_ns": 1696913792358267, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "blue", + "status": "aborted" + }, + "replacement_muted": { + "state": { + "busy": false, + "config": { + "cut_point_ms": 900, + "muted": true, + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261008-webm-transition\\build-transition\\continuous-regression-fixtures\\second-\u00e9ffect.webm", + "volume": 0.20000000298023224 + }, + "configured": true, + "last_switch": { + "command_id": "after-abort", + "frame_id": 367, + "lane_id": "B", + "pts_ns": 1696916025691578, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "blue", + "status": "completed" + }, + "media_duration_ms": 1508, + "operational": true, + "preview_overlay": {}, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "transition_duration_ms": 1758 + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261008-webm-transition\\build-transition\\continuous-regression-fixtures\\pulsar-20261008-224600.mkv", + "audio_rms": 0.0 + }, + "media_gain": { + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261008-webm-transition\\build-transition\\continuous-regression-fixtures\\pulsar-20261008-224603.mkv", + "audio_rms": 0.018612971529364586, + "ratio_to_half_gain": 0.3991944414261243 + }, + "clear_cut": { + "result": { + "command_id": "clear-cut", + "frame_id": 501, + "lane_id": "B", + "pts_ns": 1696920492358200, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "blue", + "status": "completed" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261008-webm-transition\\build-transition\\continuous-regression-fixtures\\pulsar-20261008-224606.mkv", + "audio_rms": 0.0 + }, + "mp4": { + "ready": { + "busy": false, + "config": { + "cut_point_ms": 700, + "muted": false, + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261008-webm-transition\\build-transition\\continuous-regression-fixtures\\fixture.mp4", + "volume": 0.5 + }, + "configured": true, + "last_switch": { + "command_id": "noop", + "lane_id": "B", + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "blue", + "status": "noop" + }, + "media_duration_ms": 1500, + "operational": true, + "preview_overlay": {}, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "transition_duration_ms": 1750 + }, + "result": { + "command_id": "mp4-program", + "frame_id": 585, + "lane_id": "B", + "pts_ns": 1696923292358172, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "red", + "status": "completed" + } + }, + "terminal_events": [ + { + "command_id": "same-program", + "frame_id": 92, + "lane_id": "A", + "pts_ns": 1696906859025003, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "green", + "status": "completed" + }, + { + "command_id": "same-preview", + "frame_id": 173, + "lane_id": "B", + "pts_ns": 1696909559024976, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "red", + "status": "completed" + }, + { + "command_id": "abort-me", + "frame_id": 300, + "lane_id": "B", + "pts_ns": 1696913792358267, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "blue", + "status": "aborted" + }, + { + "command_id": "after-abort", + "frame_id": 367, + "lane_id": "B", + "pts_ns": 1696916025691578, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "blue", + "status": "completed" + }, + { + "command_id": "lower-gain", + "frame_id": 462, + "lane_id": "B", + "pts_ns": 1696919192358213, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "red", + "status": "completed" + }, + { + "command_id": "clear-cut", + "frame_id": 501, + "lane_id": "B", + "pts_ns": 1696920492358200, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "blue", + "status": "completed" + }, + { + "command_id": "mp4-program", + "frame_id": 585, + "lane_id": "B", + "pts_ns": 1696923292358172, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-cc409b4f2e07e6bd", + "scene_name": "red", + "status": "completed" + } + ], + "shutdown": { + "exit_code": 0, + "forced": false + } +} \ No newline at end of file diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T204920Z-engine-activation-attempt.json b/evidence/local-20261008-webm-transition/eleven/20261008T204920Z-engine-activation-attempt.json new file mode 100644 index 00000000..b5634aa3 --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T204920Z-engine-activation-attempt.json @@ -0,0 +1,23 @@ +{ + "time": "2026-10-08T20:49:20.913Z", + "hostPid": 5392, + "oldPid": 24624, + "newPid": 20356, + "exe": "C:/Users/Mathias/AppData/Roaming/prism-dev/components/pulsar/active/bin/64bit/pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "selected": "06c5c92a-66f8-4431-92df-374dc56fab51", + "status": { + "pulsarRunning": true, + "pulsarPort": 52536, + "libobsVersion": "32.1.2-47-g76c902175-modified-pulsar", + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recording": { + "active": false, + "paused": false + }, + "activationError": null +} diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T205739Z-active-runtime.json b/evidence/local-20261008-webm-transition/eleven/20261008T205739Z-active-runtime.json new file mode 100644 index 00000000..2019101d --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T205739Z-active-runtime.json @@ -0,0 +1,77 @@ +{ + "time": "2026-10-08T20:57:39.258Z", + "hostPid": 33260, + "hostCwd": "D:\\Documents\\Zab\\Prism\\.worktrees\\eleven-local-20261008-scene-sync\\.cache\\jump-session", + "nativePid": 44836, + "nativeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "websocketSha256": "2604e4038f873104aefafd9ec23d24aa4887229d60a04184d009206a7d53288d", + "runtimeInstanceId": "prism-45138b6f75f74ccd9ae45aae001d90a7", + "selected": "06c5c92a-66f8-4431-92df-374dc56fab51", + "native": { + "busy": false, + "config": { + "cut_point_ms": 875, + "muted": false, + "path": "D:/Documents/Zab/Artifacts/2026-10-08/zab-transition-trace-sound/zab-smooth-alpha.webm", + "volume": 1 + }, + "configured": true, + "last_switch": {}, + "media_duration_ms": 2008, + "operational": true, + "preview_overlay": { + "channel": "preview", + "command_id": "preview-34a7cca1-a7da-47e1-8abc-88dfcafdff65", + "cover_kind": "outgoing_frame", + "cut_time_ms": 894, + "frame_id": 6525, + "media_paused": false, + "media_time_ms": 2014, + "operational": true, + "playback_start_frame_id": 6399, + "playback_start_pts_ns": 1697450225362466, + "pts_ns": 1697452325362382, + "runtime_instance_id": "prism-45138b6f75f74ccd9ae45aae001d90a7", + "start_frame_id": 6380, + "start_pts_ns": 1697449892029146, + "status": "completed" + }, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "prism-45138b6f75f74ccd9ae45aae001d90a7", + "transition_duration_ms": 2258 + }, + "status": { + "pulsarRunning": true, + "pulsarPort": 63289, + "libobsVersion": "32.1.2-47-g76c902175-modified-pulsar", + "activeDestinations": [], + "activeDestinationLabels": [], + "captureMode": null, + "state": "idle" + }, + "recording": { + "active": false, + "paused": false + }, + "dom": { + "stage": { + "previewLane": "preview", + "previewDualReturn": "false", + "previewLaneTransition": "false", + "previewActivation": "ready", + "previewUrlVersion": "1", + "previewRenderReady": "true", + "previewSceneId": "06c5c92a-66f8-4431-92df-374dc56fab51" + }, + "video": { + "width": 1920, + "height": 1080, + "ready": 4 + } + } +} diff --git a/evidence/local-20261008-webm-transition/eleven/20261008T205800Z-continuous-report.md b/evidence/local-20261008-webm-transition/eleven/20261008T205800Z-continuous-report.md new file mode 100644 index 00000000..3a83aa8f --- /dev/null +++ b/evidence/local-20261008-webm-transition/eleven/20261008T205800Z-continuous-report.md @@ -0,0 +1,87 @@ +# Continuous cockpit Preview transition + +Author: Eleven, thread `01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7`. +Authority: requested transition repair, no ADR or Computer Use; existing authority +to push the bounded change and activate/test it locally. Dedicated existing +branch `eleven/local-20261008-webm-transition`. Canonical checkout preserved. + +## Cause and change + +The preceding Preview protocol paused the configured media at the cut point +while Prism prepared Orion and reconciled native captures. Observed preparation +latency was therefore inserted in the middle of the animated asset. + +BeginPreview now installs a private overlay and captures the outgoing composite +in one canvas-sized GPU texture. The live composite stays hot behind that image. +The cover is acknowledged only after capture and first-frame decoder readiness. +EndPreview(false), after host preparation, starts uninterrupted playback at a +native frame boundary. The media clock reveals the incoming composite at the +configured cut without pausing or seeking. Completion/abort restore PreviewView +and release the snapshot, overlay and decoder. Begin/End/runtime/command wire +shapes and host sequencing remain compatible with the existing Prism host. + +An isolated test exposed late ffmpeg stop callbacks truncating immediate replay. +Each Preview operation now owns a fresh private decoder cloned from the +configured media settings; previous Preview/Take callbacks cannot stop it. +The configured Take stinger, Program audio, same-lane role map and no-default +asset behavior are preserved. Preview media remains muted. + +## Criteria and evidence + +| Criterion | Risk | Command/proof | Result | +|---|---|---|---| +| Delayed preparation happens before playback | Mid-video pause | `python scripts/probe-preview-transition.py --exe build-upstream/rundir/RelWithDebInfo/bin/64bit/pulsar.exe --output build-transition/continuous-preview-fixtures --report evidence/local-20261008-webm-transition/eleven/20261008T204416Z-continuous-native.json` | PASS: preparation delays 800/0/350 ms; continuous clock through cut | +| Immediate replay and both abort phases | Late decoder callbacks or retained views | Same synthetic native probe; source destruction observed for all five snapshot/decoder pairs | PASS: 3 completed + 2 aborted; graceful exit 0, no forced native kill | +| Program/Take, alpha, audio and configuration behavior | Shared stinger regression | `python scripts/probe-configurable-transition.py --exe build-upstream/rundir/RelWithDebInfo/bin/64bit/pulsar.exe --output build-transition/continuous-regression-fixtures`; `20261008T204800Z-native-regressions.json` | PASS: same-lane Program/Preview, alpha/audio, TakeCommitted frame/PTS, abort/replay, replace, mute/gain, MP4, clear, invalid media | +| Native protocol and lifecycle | Contract/admission/shutdown regression | `20261008T204747Z-scene-contracts.txt`, `20261008T204716Z-ctest.txt` | 101 pytest contracts + 4 selected CTest checks PASS | +| Actual Prism native return | Mock-only continuity claim | Prism branch evidence `evidence/local-20261008-media-transition/eleven/20261008T205453Z-continuous-rail.json` and two captured native video PNGs | PASS: editable -> Launch -> editable, cut windows advance 500/520 ms, 8/9 distinct pixel hashes, 29/30 HTML video frames, no media pause | +| Activated candidate and idle restoration | Different runtime/binary, operator state lost | `20261008T205739Z-active-runtime.json` | Exact candidate SHA below; configured/ready/operational, busy false, original selection restored, native video 1920x1080 | + +Native build: `cmake --build build-transition --config RelWithDebInfo --target +pulsar-headless --parallel 6`, successful. Selected CTest: transition-controller, +media-config, RTWQ lifecycle and headless WebSocket quiesce, all four passed. +The earlier unrelated CEF grace fixture requires a lifecycle marker absent from +the bundled browser DLL; its previously documented baseline limitation was not +changed, bypassed or retested as part of this Preview repair. + +The two initial failed synthetic attempts are retained as +`20261008T204015Z-native-first-failure.json` and +`20261008T204136Z-native-replay-failure.json`, with their distinct previous +candidate hash. They led to decoder isolation; the final native probe passed. + +## Activation and rollback + +Active executable SHA-256: +`fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8`. +WebSocket DLL remains +`2604e4038f873104aefafd9ec23d24aa4887229d60a04184d009206a7d53288d`. +Only `pulsar.exe` was updated in the existing approved runtime directory +`C:/Users/Mathias/AppData/Roaming/prism-dev/components/pulsar/active`. + +The first same-host engine shutdown/ensure attempt reused stale Preview/runtime +state (`20261008T204920Z-engine-activation-attempt.json`). A fresh Main startup +restored readiness. Native PID 20356 exited before the already-quitting Main +PID 5392 required targeted termination. No broad process kill was used. +The same active scene-sync session source snapshot/environment were preserved; +none of its product files were edited. Previously authorized +`PC-LM1E Camera (0458:6006)` was mapped again through the existing startup IPC. +Final Main PID 33260/native PID 44836 remained idle, with no active destinations +or recording. Inspector wrappers were restored and the temporary engine binding +removed after the real rail proof. + +Rollback pair: `D:/Documents/Zab/Artifacts/backups/2026-10-08/pulsar-continuous-preview/` +(`pulsar.exe`, `obs-websocket.dll`). Stop the idle session gracefully, restore the +pair to its existing runtime paths, and start that same session. No media +preference or supplied WebM was changed. + +## Limits + +Actual Live was not started. The broad media regression probe recorded only +synthetic colors/tone in its isolated process; the user's Prism session was not +recorded. During preparation the outgoing scene is deliberately a captured +still; the transition video begins only after readiness. Opacity at the chosen +cut point remains a property of the operator's asset. The measured native and +pixel evidence proves the two tested rail switches, not all possible files, +hardware loads or scene graphs. Local validation is distinct from CI or a main +branch merge. Worktree cleanup remains policy-blocked from the prior task; +no new cleanup attempt was made. diff --git a/plugins/pulsar-frontend-stub/CMakeLists.txt b/plugins/pulsar-frontend-stub/CMakeLists.txt index f7eb3ba6..a64228ea 100644 --- a/plugins/pulsar-frontend-stub/CMakeLists.txt +++ b/plugins/pulsar-frontend-stub/CMakeLists.txt @@ -72,6 +72,7 @@ add_library(pulsar-frontend-stub STATIC src/pulsar-frontend-stub.cpp src/pulsar-media-transition.cpp src/pulsar-media-transition-config.cpp + src/pulsar-preview-frame.cpp ) target_include_directories(pulsar-frontend-stub PUBLIC diff --git a/plugins/pulsar-frontend-stub/README.md b/plugins/pulsar-frontend-stub/README.md index 66fd93b8..8f97635a 100644 --- a/plugins/pulsar-frontend-stub/README.md +++ b/plugins/pulsar-frontend-stub/README.md @@ -14,6 +14,16 @@ replacement without changing the role map for the latter. The historical environment-only demo stinger described below remains an explicit opt-in; there is no automatically selected demo path. +`src/pulsar-preview-frame.cpp` owns the single canvas-sized GPU capture used +while the host prepares a persistent cockpit composite in place. It registers +its source kind at frontend initialization and renders/captures on the OBS +graphics thread. The frontend owns the private overlay's lifetime; the media +owner clones one bounded Preview decoder per operation. Host readiness then +starts continuous playback, with an incoming-composite reveal at the media +cut point and native restoration on completion/abort. The decoder isolation +prevents prior asynchronous stop callbacks from truncating immediate replays. +`scripts/probe-preview-transition.py` covers that runtime contract. + `libobs` exposes a `frontend-api` layer (`obs-frontend-api.dll`) whose function table is filled by whichever frontend is running — the OBS Studio Qt UI in upstream, this stub in Pulsar. Without callbacks set, diff --git a/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h b/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h index 2e3cc7ce..9108fa7a 100644 --- a/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h +++ b/plugins/pulsar-frontend-stub/include/pulsar-media-transition.h @@ -31,6 +31,8 @@ class MediaTransition { bool configured() const { return source_ != nullptr; } nlohmann::json state() const; void preload_tick(); // video thread only; never starts playback or audio + static void preload_frame(obs_source_t *media); // same contract for a private Preview decoder + obs_source_t *create_preview_playback() const; // owned reference; isolated from stinger stop callbacks obs_source_t *prepare(bool preview, uint32_t &duration_ms, std::string &error); void finish(); obs_source_t *playback_source() const { return media(); } // borrowed, lane mutex held diff --git a/plugins/pulsar-frontend-stub/include/pulsar-preview-frame.h b/plugins/pulsar-frontend-stub/include/pulsar-preview-frame.h new file mode 100644 index 00000000..cc7dce38 --- /dev/null +++ b/plugins/pulsar-frontend-stub/include/pulsar-preview-frame.h @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace pulsar_transition { +// One bounded GPU frame for a persistent composite. The factory is registered +// at frontend startup. Capture/render happens only on the OBS graphics thread. +void register_preview_frame_source(); +obs_source_t *create_preview_frame(obs_source_t *base, uint32_t width, uint32_t height); +bool preview_frame_ready(obs_source_t *source); +} diff --git a/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp b/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp index daee83a4..4fb12877 100644 --- a/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp +++ b/plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp @@ -106,6 +106,7 @@ #include "pulsar-dual-lane-config.h" #include "pulsar-transition-controller.h" #include "pulsar-media-transition.h" +#include "pulsar-preview-frame.h" #include "pulsar-dual-lane-control.h" #include "pulsar-runtime-telemetry.h" #include "pulsar-runtime-telemetry-signals.h" @@ -2947,6 +2948,7 @@ class PulsarFrontendAPI : public obs_frontend_callbacks { void previewMediaTickLocked(); void clearPreviewMediaLocked(); static void OnPreviewMediaStarted(void *param, uint64_t frameId, uint64_t ptsNs); + static void OnPreviewMediaPlaybackStarted(void *param, uint64_t frameId, uint64_t ptsNs); static void OnPreviewMediaRestored(void *param, uint64_t frameId, uint64_t ptsNs); static void OnLaneSwitchCommitted(void *param, uint64_t frameId, uint64_t ptsNs); static void OnSceneSwitchPreviewVideoFrame(void *param, struct video_data *frame); @@ -3914,9 +3916,15 @@ class PulsarFrontendAPI : public obs_frontend_callbacks { json lastLaneSwitch = nullptr; obs_scene_t *previewMediaOverlay = nullptr; obs_source_t *previewMediaBase = nullptr; + obs_source_t *previewMediaFrame = nullptr; + obs_source_t *previewMediaPlayback = nullptr; + obs_sceneitem_t *previewMediaHeldItem = nullptr; + obs_sceneitem_t *previewMediaLiveItem = nullptr; + obs_sceneitem_t *previewMediaVideoItem = nullptr; json previewMediaOutcome = nullptr; uint64_t previewMediaDeadlineNs = 0; bool previewMediaReleased = false; + bool previewMediaPlaybackStarted = false; bool previewMediaAborted = false; bool previewMediaRestoreQueued = false; std::atomic recordingPaused{false}; @@ -4493,8 +4501,9 @@ json PulsarFrontendAPI::configureMediaTransition(const json &config) } // The persistent cockpit wire updates one composite in place. Overlay the -// actual PreviewView, hold a decoded cover frame, then reopen after the host -// has admitted Orion and reconciled native sources. Program is never rebound +// actual PreviewView with an outgoing GPU frame while the host admits Orion +// and reconciles native sources. Only then play the media without pausing it. +// The visual cut reveals the prepared live composite. Program is never rebound // to this overlay and its audio never receives the preview media child. json PulsarFrontendAPI::beginPreviewMedia(const std::string &commandId) { @@ -4513,24 +4522,41 @@ json PulsarFrontendAPI::beginPreviewMedia(const std::string &commandId) uint32_t duration = 0; std::string error; if (!mediaTransition.prepare(true, duration, error)) return {{"error", error}}; - auto *media = mediaTransition.playback_source(); + // A fresh bounded Preview decoder cannot receive a late stop callback + // from the previous overlay or from the independent Take stinger. + previewMediaPlayback = mediaTransition.create_preview_playback(); + auto *media = previewMediaPlayback; + if (!media) return {{"error", "DECODER_UNAVAILABLE"}}; + obs_video_info video = {}; + obs_get_video_info(&video); + previewMediaFrame = pulsar_transition::create_preview_frame(base, video.base_width, video.base_height); + if (!previewMediaFrame) { + clearPreviewMediaLocked(); + return {{"error", "PREVIEW_FRAME_UNAVAILABLE"}}; + } previewMediaOverlay = obs_scene_create_private("PulsarPreviewMediaOverlay"); - if (!previewMediaOverlay) return {{"error", "PREVIEW_OVERLAY_UNAVAILABLE"}}; + if (!previewMediaOverlay) { + clearPreviewMediaLocked(); + return {{"error", "PREVIEW_OVERLAY_UNAVAILABLE"}}; + } previewMediaBase = obs_source_get_ref(base); - obs_scene_add(previewMediaOverlay, base); + previewMediaLiveItem = obs_scene_add(previewMediaOverlay, base); + previewMediaHeldItem = obs_scene_add(previewMediaOverlay, previewMediaFrame); auto *item = obs_scene_add(previewMediaOverlay, media); - if (!item) { + if (!item || !previewMediaLiveItem || !previewMediaHeldItem) { clearPreviewMediaLocked(); return {{"error", "PREVIEW_OVERLAY_UNAVAILABLE"}}; } - obs_video_info video = {}; - obs_get_video_info(&video); + previewMediaVideoItem = item; + obs_sceneitem_set_visible(previewMediaLiveItem, false); + obs_sceneitem_set_visible(previewMediaVideoItem, false); vec2 bounds; vec2_set(&bounds, static_cast(video.base_width), static_cast(video.base_height)); obs_sceneitem_set_bounds_type(item, OBS_BOUNDS_STRETCH); obs_sceneitem_set_bounds(item, &bounds); obs_sceneitem_set_bounds_alignment(item, OBS_ALIGN_TOP | OBS_ALIGN_LEFT); previewMediaReleased = previewMediaAborted = previewMediaRestoreQueued = false; + previewMediaPlaybackStarted = false; previewMediaDeadlineNs = os_gettime_ns() + (uint64_t{duration} + 20000) * 1000000ULL; const char *runtime = std::getenv("PULSAR_RUNTIME_INSTANCE_ID"); previewMediaOutcome = {{"command_id", commandId}, {"runtime_instance_id", runtime ? runtime : "pulsar-runtime"}, @@ -4550,13 +4576,25 @@ void PulsarFrontendAPI::OnPreviewMediaStarted(void *param, uint64_t frameId, uin auto *self = static_cast(param); std::lock_guard lock(self->dualLaneMutex); if (!self->previewMediaOverlay) return; - obs_source_media_play_pause(self->mediaTransition.playback_source(), false); - obs_source_media_restart(self->mediaTransition.playback_source()); - self->previewMediaOutcome["status"] = "closing"; + self->previewMediaOutcome["status"] = "preparing"; self->previewMediaOutcome["start_frame_id"] = frameId; self->previewMediaOutcome["start_pts_ns"] = ptsNs; } +void PulsarFrontendAPI::OnPreviewMediaPlaybackStarted(void *param, uint64_t frameId, uint64_t ptsNs) +{ + auto *self = static_cast(param); + std::lock_guard lock(self->dualLaneMutex); + if (!self->previewMediaOverlay || self->previewMediaAborted) return; + obs_sceneitem_set_visible(self->previewMediaVideoItem, true); + obs_source_media_restart(self->previewMediaPlayback); + self->previewMediaPlaybackStarted = true; + self->previewMediaOutcome["status"] = "closing"; + self->previewMediaOutcome["media_time_ms"] = 0; + self->previewMediaOutcome["playback_start_frame_id"] = frameId; + self->previewMediaOutcome["playback_start_pts_ns"] = ptsNs; +} + json PulsarFrontendAPI::previewMediaResult(const std::string &commandId) { std::lock_guard lock(dualLaneMutex); @@ -4570,13 +4608,19 @@ json PulsarFrontendAPI::endPreviewMedia(const std::string &commandId, bool abort if (previewMediaOutcome.is_null() || previewMediaOutcome["command_id"] != commandId) return {{"error", "COMMAND_NOT_FOUND"}}; if (!previewMediaOverlay) return previewMediaOutcome; + if (previewMediaReleased && !abort) return previewMediaOutcome; if (!abort && previewMediaOutcome["status"] != "covered" && !previewMediaReleased) return {{"error", "PREVIEW_NOT_COVERED"}}; previewMediaReleased = true; previewMediaAborted = previewMediaAborted || abort; if (!abort) { - obs_source_media_play_pause(mediaTransition.playback_source(), false); - previewMediaOutcome["status"] = "opening"; + previewMediaOutcome["status"] = "starting"; + OBSSourceAutoRelease program = obs_view_get_source(programView, 0); + if (!obs_view_queue_atomic_swap_with_floor(programView, 0, program, previewView, 0, + obs_scene_get_source(previewMediaOverlay), os_gettime_ns(), OnPreviewMediaPlaybackStarted, this)) { + previewMediaAborted = true; + return {{"error", "ATOMIC_SWAP_REJECTED"}}; + } } return previewMediaOutcome; } @@ -4585,9 +4629,16 @@ void PulsarFrontendAPI::clearPreviewMediaLocked() { if (previewMediaOverlay) obs_scene_release(previewMediaOverlay); previewMediaOverlay = nullptr; + previewMediaHeldItem = previewMediaLiveItem = previewMediaVideoItem = nullptr; + if (previewMediaFrame) obs_source_release(previewMediaFrame); + previewMediaFrame = nullptr; if (previewMediaBase) obs_source_release(previewMediaBase); previewMediaBase = nullptr; - if (mediaTransition.playback_source()) obs_source_media_stop(mediaTransition.playback_source()); + if (previewMediaPlayback) { + obs_source_media_stop(previewMediaPlayback); + obs_source_release(previewMediaPlayback); + } + previewMediaPlayback = nullptr; mediaTransition.finish(); } @@ -4605,19 +4656,32 @@ void PulsarFrontendAPI::OnPreviewMediaRestored(void *param, uint64_t frameId, ui void PulsarFrontendAPI::previewMediaTickLocked() { if (!previewMediaOverlay || previewMediaRestoreQueued) return; - auto *media = mediaTransition.playback_source(); + auto *media = previewMediaPlayback; + pulsar_transition::MediaTransition::preload_frame(media); if (os_gettime_ns() >= previewMediaDeadlineNs || obs_source_media_get_state(media) == OBS_MEDIA_STATE_ERROR) { previewMediaAborted = true; previewMediaOutcome["failure_reason"] = "PREVIEW_TRANSITION_TIMEOUT_OR_DECODE_ERROR"; } - if (!previewMediaReleased && previewMediaOutcome["status"] == "closing" && - obs_source_media_get_time(media) >= mediaTransition.cut_point_ms()) { - obs_source_media_play_pause(media, true); + if (!previewMediaReleased && previewMediaOutcome["status"] == "preparing" && + pulsar_transition::preview_frame_ready(previewMediaFrame) && + obs_source_get_width(media) && obs_source_get_height(media)) { previewMediaOutcome["status"] = "covered"; - previewMediaOutcome["cut_time_ms"] = obs_source_media_get_time(media); + previewMediaOutcome["cover_kind"] = "outgoing_frame"; + } + if (previewMediaPlaybackStarted) { + const auto time = obs_source_media_get_time(media); + previewMediaOutcome["media_time_ms"] = (std::max)(time, + previewMediaOutcome.value("media_time_ms", int64_t{0})); + previewMediaOutcome["media_paused"] = obs_source_media_get_state(media) == OBS_MEDIA_STATE_PAUSED; + if (previewMediaOutcome["status"] == "closing" && time >= mediaTransition.cut_point_ms()) { + obs_sceneitem_set_visible(previewMediaLiveItem, true); + obs_sceneitem_set_visible(previewMediaHeldItem, false); + previewMediaOutcome["status"] = "opening"; + previewMediaOutcome["cut_time_ms"] = time; + } } const auto state = obs_source_media_get_state(media); - if (!previewMediaAborted && (!previewMediaReleased || + if (!previewMediaAborted && (!previewMediaPlaybackStarted || (state != OBS_MEDIA_STATE_ENDED && state != OBS_MEDIA_STATE_STOPPED))) return; OBSSourceAutoRelease program = obs_view_get_source(programView, 0); previewMediaRestoreQueued = obs_view_queue_atomic_swap_with_floor(programView, 0, program, @@ -7793,6 +7857,7 @@ extern "C" void pulsar_frontend_init(void) g_rollbackMarkerWriter.start(); g_dualLaneControlBridge.install(); g_runtimeTelemetry.install(); + pulsar_transition::register_preview_frame_source(); auto *api = new PulsarFrontendAPI(); g_api = api; obs_frontend_set_callbacks_internal(api); diff --git a/plugins/pulsar-frontend-stub/src/pulsar-media-transition.cpp b/plugins/pulsar-frontend-stub/src/pulsar-media-transition.cpp index 29280f44..e569968a 100644 --- a/plugins/pulsar-frontend-stub/src/pulsar-media-transition.cpp +++ b/plugins/pulsar-frontend-stub/src/pulsar-media-transition.cpp @@ -85,16 +85,35 @@ uint64_t MediaTransition::media_duration_ns() const } void MediaTransition::preload_tick() +{ + preload_frame(media()); +} + +void MediaTransition::preload_frame(obs_source_t *media) { // Inactive ffmpeg sources retain a decoded first frame, but expose zero // dimensions until that frame has a texture. Upload it on the video thread // only: doing this from GetState would invert graphics and lane locks. - if (media() && media_duration_ns() && !obs_source_get_width(media_)) { + if (media && !obs_source_get_width(media)) { calldata_t cd = {}; - proc_handler_call(obs_source_get_proc_handler(media_), "preload_first_frame", &cd); + proc_handler_call(obs_source_get_proc_handler(media), "preload_first_frame", &cd); calldata_free(&cd); - obs_source_show_preloaded_video(media_); + obs_source_show_preloaded_video(media); + } +} + +obs_source_t *MediaTransition::create_preview_playback() const +{ + if (!media()) return nullptr; + OBSDataAutoRelease original = obs_source_get_settings(media_); + OBSDataAutoRelease settings = obs_data_create(); + obs_data_apply(settings, original); + auto *playback = obs_source_create_private("ffmpeg_source", "PulsarPreviewTransitionVideo", settings); + if (playback) { + obs_source_set_volume(playback, config_.volume); + obs_source_set_muted(playback, true); } + return playback; } std::string MediaTransition::readiness_error() const diff --git a/plugins/pulsar-frontend-stub/src/pulsar-preview-frame.cpp b/plugins/pulsar-frontend-stub/src/pulsar-preview-frame.cpp new file mode 100644 index 00000000..c76dc2c6 --- /dev/null +++ b/plugins/pulsar-frontend-stub/src/pulsar-preview-frame.cpp @@ -0,0 +1,112 @@ +#include "pulsar-preview-frame.h" +#include +#include +#include +#include + +namespace pulsar_transition { +namespace { +constexpr const char *kSourceId = "pulsar_preview_frame"; +struct Frame { + obs_source_t *owner = nullptr; + obs_source_t *base = nullptr; + gs_texrender_t *texture = nullptr; + uint32_t width = 0; + uint32_t height = 0; + std::atomic ready{false}; +}; + +const char *name(void *) { return "Pulsar outgoing Preview frame"; } +void *create(obs_data_t *settings, obs_source_t *owner) +{ + auto *frame = new Frame; + frame->owner = owner; + frame->base = obs_get_source_by_name(obs_data_get_string(settings, "base_source")); + frame->width = static_cast(obs_data_get_int(settings, "width")); + frame->height = static_cast(obs_data_get_int(settings, "height")); + if (!frame->base || !frame->width || !frame->height) { + if (frame->base) obs_source_release(frame->base); + delete frame; + return nullptr; + } + return frame; +} +void destroy(void *data) +{ + auto *frame = static_cast(data); + obs_enter_graphics(); + gs_texrender_destroy(frame->texture); + obs_leave_graphics(); + obs_source_release(frame->base); + delete frame; +} +uint32_t width(void *data) { return static_cast(data)->width; } +uint32_t height(void *data) { return static_cast(data)->height; } +void active(void *data, obs_source_enum_proc_t callback, void *param) +{ + // Keep the real composite hot while it is prepared behind the captured + // image. Showing its live item before hiding this source avoids producer + // deactivation/restart at the visual cut. + auto *frame = static_cast(data); + callback(frame->owner, frame->base, param); +} +void render(void *data, gs_effect_t *) +{ + auto *frame = static_cast(data); + if (!frame->ready.load(std::memory_order_acquire)) { + if (!frame->texture) frame->texture = gs_texrender_create(GS_RGBA, GS_ZS_NONE); + if (!frame->texture) return; + gs_blend_state_push(); + gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); + const bool begun = gs_texrender_begin(frame->texture, frame->width, frame->height); + if (begun) { + vec4 blank; + vec4_zero(&blank); + gs_clear(GS_CLEAR_COLOR, &blank, 0.0f, 0); + gs_ortho(0.0f, static_cast(frame->width), 0.0f, + static_cast(frame->height), -100.0f, 100.0f); + obs_source_video_render(frame->base); + gs_texrender_end(frame->texture); + frame->ready.store(true, std::memory_order_release); + } + gs_blend_state_pop(); + if (!begun) return; + } + auto *effect = obs_get_base_effect(OBS_EFFECT_DEFAULT); + gs_effect_set_texture(gs_effect_get_param_by_name(effect, "image"), gs_texrender_get_texture(frame->texture)); + while (gs_effect_loop(effect, "Draw")) gs_draw_sprite(gs_texrender_get_texture(frame->texture), 0, 0, 0); +} +} + +void register_preview_frame_source() +{ + obs_source_info info = {}; + info.id = kSourceId; + info.type = OBS_SOURCE_TYPE_INPUT; + info.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW; + info.get_name = name; + info.create = create; + info.destroy = destroy; + info.get_width = width; + info.get_height = height; + info.video_render = render; + info.enum_active_sources = active; + obs_register_source(&info); +} + +obs_source_t *create_preview_frame(obs_source_t *base, uint32_t width, uint32_t height) +{ + OBSDataAutoRelease settings = obs_data_create(); + obs_data_set_string(settings, "base_source", obs_source_get_name(base)); + obs_data_set_int(settings, "width", width); + obs_data_set_int(settings, "height", height); + return obs_source_create_private(kSourceId, "PulsarOutgoingPreviewFrame", settings); +} + +bool preview_frame_ready(obs_source_t *source) +{ + if (!source || std::strcmp(obs_source_get_id(source), kSourceId) != 0) return false; + auto *frame = static_cast(obs_obj_get_data(source)); + return frame && frame->ready.load(std::memory_order_acquire); +} +} diff --git a/scripts/probe-preview-transition.py b/scripts/probe-preview-transition.py new file mode 100644 index 00000000..b36ff38a --- /dev/null +++ b/scripts/probe-preview-transition.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +"""Native Preview continuity regression probe, using only synthetic sources. + +No Prism session, camera, desktop capture, recording or stream is touched. +--output owns temporary generated media and --report names durable JSON evidence. +Reuses the existing public WebSocket/process lifecycle and alpha fixture helpers. +""" +from __future__ import annotations + +import argparse +import asyncio +import base64 +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import sys +import time + +import websockets + +spec = importlib.util.spec_from_file_location( + "preview_probe_helpers", Path(__file__).with_name("probe-configurable-transition.py") +) +helpers = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = helpers +spec.loader.exec_module(helpers) + + +async def exercise(args, proc, report): + proc.spawn() + ready = await asyncio.to_thread(proc.wait_for, helpers.process.READY_RE, 45) + uri, password = ready.groups() + async with websockets.connect(uri, max_size=16 * 1024 * 1024) as ws: + hello = json.loads(await ws.recv())["d"] + identify = {"rpcVersion": 1, "eventSubscriptions": 0x7ff} + if "authentication" in hello: + auth = hello["authentication"] + secret = base64.b64encode(hashlib.sha256((password + auth["salt"]).encode()).digest()).decode() + identify["authentication"] = base64.b64encode( + hashlib.sha256((secret + auth["challenge"]).encode()).digest() + ).decode() + await ws.send(json.dumps({"op": 1, "d": identify})) + assert json.loads(await ws.recv())["op"] == 2 + wire = helpers.Wire(ws) + native = await wire.vendor("GetState") + assert native["operational"] and not native["configured"], native + guard = {"runtime_instance_id": native["runtime_instance_id"]} + report["runtime_instance_id"] = native["runtime_instance_id"] + await wire.call("CreateScene", {"sceneName": "ZabPreviewComposite"}) + await wire.call("CreateInput", { + "sceneName": "ZabPreviewComposite", "inputName": "synthetic-preview", + "inputKind": "color_source_v3", "sceneItemEnabled": True, + "inputSettings": {"color": 0xff2828dc, "width": 1920, "height": 1080}, + }) + assert (await wire.vendor("SetPreviewComposite", {"enabled": True}, "pulsar-scene-switch"))["ok"] + program = (await wire.call("GetCurrentProgramScene"))["currentProgramSceneName"] + await wire.vendor("Configure", {**guard, "config": { + "path": str(args.output / "fixture.webm"), "cut_point_ms": 700, + "volume": .5, "muted": False, + }}) + await wire.poll("GetState", {}, lambda s: s.get("ready")) + report["runs"] = [] + + for index, delay in enumerate([.8, 0, .35]): + command = {**guard, "command_id": f"continuous-preview-{index}"} + start = time.perf_counter() + accepted = await wire.vendor("BeginPreview", command) + assert "error" not in accepted, accepted + covered = await wire.poll("GetPreviewResult", command, lambda s: s.get("status") == "covered") + capture_ms = (time.perf_counter() - start) * 1000 + assert covered["cover_kind"] == "outgoing_frame", covered + assert "playback_start_frame_id" not in covered, covered + assert "media_time_ms" not in covered, covered + # Exercise the same in-place reconciliation Prism performs while + # the captured outgoing frame remains attached to PreviewView. + await wire.call("SetInputSettings", {"inputName": "synthetic-preview", + "inputSettings": {"color": 0xff28dc28 if index % 2 == 0 else 0xffdc2828}}) + assert (await wire.vendor("SetPreviewComposite", {"enabled": True}, "pulsar-scene-switch"))["ok"] + await asyncio.sleep(delay) + still = await wire.vendor("GetPreviewResult", command) + assert still["status"] == "covered" and "media_time_ms" not in still, still + assert (await wire.vendor("GetState"))["busy"] + playback_start = time.perf_counter() + released = await wire.vendor("EndPreview", {**command, "abort": False}) + assert "error" not in released, released + samples = [] + deadline = playback_start + 5 + while time.perf_counter() < deadline: + state = await wire.vendor("GetPreviewResult", command) + samples.append({"elapsed_ms": (time.perf_counter() - playback_start) * 1000, **state}) + assert state.get("media_paused") is not True, state + if state["status"] in ["completed", "aborted", "failed"]: + break + await asyncio.sleep(.04) + final = samples[-1] + report["last_attempt"] = {"preparation_delay_ms": delay * 1000, "samples": samples} + assert final["status"] == "completed", final + assert final["frame_id"] > final["playback_start_frame_id"] > final["start_frame_id"] > 0 + assert final["pts_ns"] > final["playback_start_pts_ns"] > final["start_pts_ns"] > 0 + phases = {s["status"] for s in samples} + assert {"closing", "opening", "completed"} <= phases, phases + times = [s["media_time_ms"] for s in samples if "media_time_ms" in s] + assert all(b >= a for a, b in zip(times, times[1:])), times + # A wall-clock plateau at the old 700ms pause would fail this bound. + around_cut = [s for s in samples if 500 <= s.get("media_time_ms", -1) <= 900] + assert len(around_cut) >= 4, around_cut + span = around_cut[-1]["elapsed_ms"] - around_cut[0]["elapsed_ms"] + advance = around_cut[-1]["media_time_ms"] - around_cut[0]["media_time_ms"] + assert advance > 200 and abs(span - advance) < 170, (span, advance) + assert 1200 < final["elapsed_ms"] < 2100, final["elapsed_ms"] + assert (await wire.call("GetCurrentProgramScene"))["currentProgramSceneName"] == program + assert (await wire.vendor("EndPreview", {**command, "abort": False}))["status"] == "completed" + report["runs"].append({"preparation_delay_ms": delay * 1000, "capture_ms": capture_ms, + "cut_window_wall_ms": span, "cut_window_media_ms": advance, "samples": samples}) + await wire.poll("GetState", {}, lambda s: s.get("ready") and not s.get("busy")) + + report["aborts"] = [] + for phase in ["preparing", "playing"]: + command = {**guard, "command_id": f"abort-{phase}"} + await wire.vendor("BeginPreview", command) + await wire.poll("GetPreviewResult", command, lambda s: s.get("status") == "covered") + if phase == "playing": + await wire.vendor("EndPreview", {**command, "abort": False}) + await wire.poll("GetPreviewResult", command, lambda s: s.get("media_time_ms", 0) >= 300) + await wire.vendor("EndPreview", {**command, "abort": True}) + result = await wire.poll("GetPreviewResult", command, lambda s: s.get("status") == "aborted") + assert result["frame_id"] > 0 and result["pts_ns"] > 0 + report["aborts"].append({"phase": phase, "result": result}) + await wire.poll("GetState", {}, lambda s: s.get("ready") and not s.get("busy")) + await wire.vendor("Clear", guard) + assert (await wire.call("GetCurrentProgramScene"))["currentProgramSceneName"] == program + report["passed"] = True + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--exe", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + args.output = args.output.resolve() + args.output.mkdir(parents=True, exist_ok=True) + args.report.parent.mkdir(parents=True, exist_ok=True) + for key in ["PULSAR_STINGER_ASSET", "PULSAR_MIC_DEVICE_ID", "PULSAR_PROCESS_AUDIO_NAME", "PULSAR_TRACE_PATH"]: + os.environ.pop(key, None) + os.environ.update(PULSAR_NATIVE_STINGER="0", PULSAR_DUAL_LANE_TRANSITIONS="0", PULSAR_LEGACY_ALIAS="disabled", + PULSAR_DIRECTSHOW_LEGACY_ALIAS="0", PULSAR_DESKTOP_AUDIO_DEVICE_ID="pulsar-synthetic-test-no-device", PULSAR_FPS="30") + helpers.fixture(args.output / "fixture.webm") + proc = helpers.process.PulsarProcess(args.exe.resolve(), "x264", args.output) + report = {"passed": False, "exe_sha256": hashlib.sha256(args.exe.read_bytes()).hexdigest()} + try: + asyncio.run(exercise(args, proc, report)) + except Exception as exc: + report["failure"] = repr(exc) + raise + finally: + try: + proc.shutdown() + report["shutdown"] = {"exit_code": proc.proc.returncode if proc.proc else None, "forced": proc.forced_kill_used} + finally: + args.report.write_bytes((json.dumps(report, indent=2) + "\n").encode()) + safe = [line for line in proc.lines if "password" not in line.lower() and "PULSAR_READY" not in line] + (args.output / "runtime.log").write_bytes(("\n".join(safe) + "\n").encode()) + assert report["shutdown"] == {"exit_code": 0, "forced": False}, report["shutdown"] + print(json.dumps({"passed": report["passed"], "report": str(args.report)})) + + +if __name__ == "__main__": + main() From e8d0ce456e1c9ee763f6f3d8049b4162ebee7d59 Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Fri, 9 Oct 2026 12:16:45 +0200 Subject: [PATCH 07/17] fix(websocket): guard invalid source screenshot allocations --- ...61009T085300Z-configurable-transition.json | 735 +++++ .../20261009T085400Z-preview-continuity.json | 2367 ++++++++++++++++ .../20261009T085500Z-synthetic-output.json | 102 + .../eleven/20261009T085600Z-cef-shutdown.json | 9 + .../20261009T090000Z-audit-checkpoint.json | 20 + .../20261009T090000Z-native-contracts.json | 20 + .../20261009T090100Z-source-manifest.json | 1090 ++++++++ ...5Z-configurable-transition-regression.json | 730 +++++ .../eleven/20261009T101625Z-fix-report.json | 28 + ...T101625Z-screenshot-filter-regression.json | 11 + .../20261009T102000Z-screenshot-before.json | 62 + .../20261009T102100Z-screenshot-after.json | 62 + .../20261009T104000Z-preview-fix-probe.json | 2396 +++++++++++++++++ plugins/pulsar-websocket/README.md | 8 + .../requesthandler/RequestHandler_Sources.cpp | 17 +- scripts/probe-source-screenshot.py | 76 + 16 files changed, 7732 insertions(+), 1 deletion(-) create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T085300Z-configurable-transition.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T085400Z-preview-continuity.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T085500Z-synthetic-output.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T085600Z-cef-shutdown.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T090000Z-audit-checkpoint.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T090000Z-native-contracts.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T090100Z-source-manifest.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T101625Z-configurable-transition-regression.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T101625Z-fix-report.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T101625Z-screenshot-filter-regression.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T102000Z-screenshot-before.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T102100Z-screenshot-after.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261009T104000Z-preview-fix-probe.json create mode 100644 scripts/probe-source-screenshot.py diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T085300Z-configurable-transition.json b/evidence/local-20261009-capability-audit/eleven/20261009T085300Z-configurable-transition.json new file mode 100644 index 00000000..35edb6d6 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T085300Z-configurable-transition.json @@ -0,0 +1,735 @@ +{ + "passed": true, + "default": { + "busy": false, + "config": {}, + "configured": false, + "last_switch": {}, + "media_duration_ms": 0, + "operational": true, + "preview_overlay": {}, + "readiness_error": "NOT_CONFIGURED", + "ready": false, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "transition_duration_ms": 0 + }, + "ready": { + "busy": false, + "config": { + "cut_point_ms": 700, + "muted": false, + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\configurable-transition-audit\\fixture.webm", + "volume": 0.5 + }, + "configured": true, + "last_switch": {}, + "media_duration_ms": 1508, + "operational": true, + "preview_overlay": {}, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "transition_duration_ms": 1758 + }, + "same_program": { + "result": { + "command_id": "same-program", + "frame_id": 83, + "lane_id": "A", + "pts_ns": 5195431772406, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "green", + "status": "completed" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\configurable-transition-audit\\pulsar-20261009-105131.mkv", + "frames": 76, + "audio_rms": 0.04666884243488312, + "centers": [ + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 212.0, + 40.0, + 35.0 + ], + [ + 203.0, + 47.0, + 35.0 + ], + [ + 197.0, + 60.0, + 33.0 + ], + [ + 193.0, + 74.0, + 32.0 + ], + [ + 193.0, + 91.0, + 31.0 + ], + [ + 195.0, + 109.0, + 28.0 + ], + [ + 201.0, + 127.0, + 28.0 + ], + [ + 212.0, + 146.0, + 27.0 + ], + [ + 226.0, + 165.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 223.0, + 173.0, + 27.0 + ], + [ + 196.0, + 169.0, + 27.0 + ], + [ + 171.0, + 169.0, + 28.0 + ], + [ + 145.0, + 173.0, + 30.0 + ], + [ + 119.0, + 179.0, + 31.0 + ], + [ + 96.0, + 186.0, + 32.0 + ], + [ + 74.0, + 194.0, + 33.0 + ], + [ + 53.0, + 203.0, + 35.0 + ], + [ + 41.0, + 212.0, + 37.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ] + ] + }, + "same_preview": { + "result": { + "command_id": "same-preview", + "frame_id": 159, + "lane_id": "B", + "pts_ns": 5197965105714, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "red", + "status": "completed" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\configurable-transition-audit\\pulsar-20261009-105134.mkv", + "audio_rms": 0.0 + }, + "take": { + "state": { + "contract": "pulsar.scene-switch.v1", + "frozen": false, + "idempotency_cache_capacity": 4096, + "idempotency_cache_entries": 2, + "operational": true, + "revisions": { + "preview": 2, + "program": 2, + "role_map": 1 + }, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "schema_version": 1, + "server_seq": 4, + "state": "ready" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\configurable-transition-audit\\pulsar-20261009-105136.mkv", + "audio_rms": 0.04644118994474411, + "committed_event": { + "command_id": "test-take-command", + "contract": "pulsar.scene-switch.v1", + "event_type": "TakeCommitted", + "frame_id": 245, + "intent_id": "test-take", + "message_type": "event", + "observed_at_monotonic_ns": 5200833323500, + "payload_sha256": "4078c4a2aa3e6eebe59e74782eb3ea7f5e7dc08529a40533c1fffec22396fb92", + "preview_lane_id": "A", + "previous_revisions": { + "preview": 2, + "program": 1, + "role_map": 0 + }, + "previous_role_map": { + "on_air": "A", + "preview": "B" + }, + "program_lane_id": "B", + "pts_ns": 5200831772352, + "revisions": { + "preview": 2, + "program": 2, + "role_map": 1 + }, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "schema_version": 1, + "server_seq": 4, + "source_lane_id": "A", + "state": "ready", + "take_command_id": "test-take-command", + "target_lane_id": "B", + "target_scene_id": "red" + } + }, + "abort": { + "command_id": "abort-me", + "frame_id": 279, + "lane_id": "B", + "pts_ns": 5201965105674, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "blue", + "status": "aborted" + }, + "replacement_muted": { + "state": { + "busy": false, + "config": { + "cut_point_ms": 900, + "muted": true, + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\configurable-transition-audit\\second-\u00e9ffect.webm", + "volume": 0.20000000298023224 + }, + "configured": true, + "last_switch": { + "command_id": "after-abort", + "frame_id": 345, + "lane_id": "B", + "pts_ns": 5204165105652, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "blue", + "status": "completed" + }, + "media_duration_ms": 1508, + "operational": true, + "preview_overlay": {}, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "transition_duration_ms": 1758 + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\configurable-transition-audit\\pulsar-20261009-105140.mkv", + "audio_rms": 0.0 + }, + "media_gain": { + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\configurable-transition-audit\\pulsar-20261009-105143.mkv", + "audio_rms": 0.018489770591259003, + "ratio_to_half_gain": 0.3961908979649049 + }, + "clear_cut": { + "result": { + "command_id": "clear-cut", + "frame_id": 475, + "lane_id": "B", + "pts_ns": 5208498438942, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "blue", + "status": "completed" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\configurable-transition-audit\\pulsar-20261009-105146.mkv", + "audio_rms": 0.0 + }, + "mp4": { + "ready": { + "busy": false, + "config": { + "cut_point_ms": 700, + "muted": false, + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\configurable-transition-audit\\fixture.mp4", + "volume": 0.5 + }, + "configured": true, + "last_switch": { + "command_id": "noop", + "lane_id": "B", + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "blue", + "status": "noop" + }, + "media_duration_ms": 1500, + "operational": true, + "preview_overlay": {}, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "transition_duration_ms": 1750 + }, + "result": { + "command_id": "mp4-program", + "frame_id": 555, + "lane_id": "B", + "pts_ns": 5211165105582, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "red", + "status": "completed" + } + }, + "terminal_events": [ + { + "command_id": "same-program", + "frame_id": 83, + "lane_id": "A", + "pts_ns": 5195431772406, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "green", + "status": "completed" + }, + { + "command_id": "same-preview", + "frame_id": 159, + "lane_id": "B", + "pts_ns": 5197965105714, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "red", + "status": "completed" + }, + { + "command_id": "abort-me", + "frame_id": 279, + "lane_id": "B", + "pts_ns": 5201965105674, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "blue", + "status": "aborted" + }, + { + "command_id": "after-abort", + "frame_id": 345, + "lane_id": "B", + "pts_ns": 5204165105652, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "blue", + "status": "completed" + }, + { + "command_id": "lower-gain", + "frame_id": 437, + "lane_id": "B", + "pts_ns": 5207231772288, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "red", + "status": "completed" + }, + { + "command_id": "clear-cut", + "frame_id": 475, + "lane_id": "B", + "pts_ns": 5208498438942, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "blue", + "status": "completed" + }, + { + "command_id": "mp4-program", + "frame_id": 555, + "lane_id": "B", + "pts_ns": 5211165105582, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-00a64615efab545a", + "scene_name": "red", + "status": "completed" + } + ], + "shutdown": { + "exit_code": 0, + "forced": false + } +} \ No newline at end of file diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T085400Z-preview-continuity.json b/evidence/local-20261009-capability-audit/eleven/20261009T085400Z-preview-continuity.json new file mode 100644 index 00000000..43d07b60 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T085400Z-preview-continuity.json @@ -0,0 +1,2367 @@ +{ + "passed": true, + "exe_sha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "runtime_instance_id": "runtime-b0b5049389e52df4", + "runs": [ + { + "preparation_delay_ms": 800.0, + "capture_ms": 63.22279999949387, + "cut_window_wall_ms": 328.93630000035046, + "cut_window_media_ms": 366, + "samples": [ + { + "elapsed_ms": 1.3116999998601386, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "starting" + }, + { + "elapsed_ms": 44.7796999997081, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 91.52009999979782, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 138.4013999995659, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 185.8025000001362, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 232.38510000010137, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 133, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 280.12699999999313, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 326.22079999964626, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 371.53200000011566, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 234, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 417.78419999991456, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 464.2996000002313, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 511.37029999972583, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 433, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 557.8613999996378, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 466, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 605.0611000000572, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 466, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 651.5977999997631, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 534, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 698.3466000001499, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 744.9340000002849, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 634, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "closing" + }, + { + "elapsed_ms": 792.4077999996371, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 839.66270000019, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 733, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 887.0788000003813, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 933.0908000001727, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 833, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 980.5341000001135, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1028.3808000003773, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 933, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1075.1098000000638, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 934, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1121.820200000002, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1033, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1169.2265000001498, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1066, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1216.6125000003376, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1263.6012000002665, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1166, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1309.803699999975, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1355.715699999564, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1266, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1401.992699999937, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1447.695500000009, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1366, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1494.9298999999883, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1541.9584000001123, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1433, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1587.8703999997015, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1635.7386999998198, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "opening" + }, + { + "elapsed_ms": 1681.2759000004007, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 96, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 46, + "playback_start_pts_ns": 5300925932085, + "pts_ns": 5302592598735, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 20, + "start_pts_ns": 5300059265427, + "status": "completed" + } + ] + }, + { + "preparation_delay_ms": 0, + "capture_ms": 108.96589999993012, + "cut_window_wall_ms": 375.55690000044706, + "cut_window_media_ms": 366, + "samples": [ + { + "elapsed_ms": 0.5998999995426857, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "starting" + }, + { + "elapsed_ms": 45.41319999952975, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_time_ms": 0, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 92.20529999947757, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 137.50859999981913, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 184.28079999921465, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 232.05939999934344, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 133, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 278.1228999992891, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 166, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 323.82069999948726, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 370.47029999939696, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 266, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 417.3842999998669, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 464.01309999964724, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 510.6596999994508, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 558.3024999996269, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 434, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 605.3668999993533, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 653.4189999993032, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 534, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 698.3219999992798, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 744.4364999992104, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 633, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "closing" + }, + { + "elapsed_ms": 792.9271999992125, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 839.6748999994088, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 733, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 886.4503999993758, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 933.97719999939, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 980.9237999998004, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 866, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1026.5540999998848, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 933, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1074.2535999997926, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1120.728899999449, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1033, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1168.6012999998638, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1034, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1214.3420999991577, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1260.770699999739, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1166, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1308.1703999996535, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1354.2601999997714, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1234, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1402.8003999992507, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1450.7865999994465, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1333, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1498.0385999997452, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1545.166099999733, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1433, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1592.7914999992936, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1639.735999999175, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "opening" + }, + { + "elapsed_ms": 1685.8323999995264, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 150, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 100, + "playback_start_pts_ns": 5302725932067, + "pts_ns": 5304392598717, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 97, + "start_pts_ns": 5302625932068, + "status": "completed" + } + ] + }, + { + "preparation_delay_ms": 350.0, + "capture_ms": 106.94609999973181, + "cut_window_wall_ms": 423.47049999989395, + "cut_window_media_ms": 400, + "samples": [ + { + "elapsed_ms": 0.5515000002560555, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "starting" + }, + { + "elapsed_ms": 46.362199999748555, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 93.17809999993187, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 140.35520000015822, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 187.23979999958829, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 233.47499999999854, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 133, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 279.3509000002814, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 166, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 326.9786999999269, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 233, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 374.9748999998701, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 266, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 422.4224999998114, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 469.0976000001683, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 515.4783999996653, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 562.2251000004326, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 434, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 608.6623999999574, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 654.3303999997079, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 534, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 700.6025000000591, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 747.4659999998039, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 666, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 795.9807999995974, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 843.2955999996921, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 891.0310000001118, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 937.3551999997289, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 833, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 985.7174999997369, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1032.1328999998514, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1079.5547999996415, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1000, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1125.6634000001213, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1033, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1172.3369999999704, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1066, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1219.1356000002997, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1133, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1265.2384000002712, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1134, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1312.0313999997961, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1358.7166999996043, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1266, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1405.68119999989, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1453.599999999824, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1366, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1499.7769000001426, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1547.776899999917, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1466, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1594.6335999997245, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1642.3736999995526, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1688.6328000000503, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 214, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "pts_ns": 5306525932029, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "completed" + } + ] + } + ], + "last_attempt": { + "preparation_delay_ms": 350.0, + "samples": [ + { + "elapsed_ms": 0.5515000002560555, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "starting" + }, + { + "elapsed_ms": 46.362199999748555, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 93.17809999993187, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 140.35520000015822, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 187.23979999958829, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 233.47499999999854, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 133, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 279.3509000002814, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 166, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 326.9786999999269, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 233, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 374.9748999998701, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 266, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 422.4224999998114, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 469.0976000001683, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 515.4783999996653, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 562.2251000004326, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 434, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 608.6623999999574, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 654.3303999997079, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 534, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 700.6025000000591, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 747.4659999998039, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 666, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "closing" + }, + { + "elapsed_ms": 795.9807999995974, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 843.2955999996921, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 891.0310000001118, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 937.3551999997289, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 833, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 985.7174999997369, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1032.1328999998514, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1079.5547999996415, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1000, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1125.6634000001213, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1033, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1172.3369999999704, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1066, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1219.1356000002997, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1133, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1265.2384000002712, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1134, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1312.0313999997961, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1358.7166999996043, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1266, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1405.68119999989, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1453.599999999824, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1366, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1499.7769000001426, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1547.776899999917, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1466, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1594.6335999997245, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1642.3736999995526, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "opening" + }, + { + "elapsed_ms": 1688.6328000000503, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 214, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 164, + "playback_start_pts_ns": 5304859265379, + "pts_ns": 5306525932029, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 151, + "start_pts_ns": 5304425932050, + "status": "completed" + } + ] + }, + "aborts": [ + { + "phase": "preparing", + "result": { + "channel": "preview", + "command_id": "abort-preparing", + "cover_kind": "outgoing_frame", + "frame_id": 218, + "operational": true, + "pts_ns": 5306659265361, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 215, + "start_pts_ns": 5306559265362, + "status": "aborted" + } + }, + { + "phase": "playing", + "result": { + "channel": "preview", + "command_id": "abort-playing", + "cover_kind": "outgoing_frame", + "frame_id": 236, + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 223, + "playback_start_pts_ns": 5306825932026, + "pts_ns": 5307259265355, + "runtime_instance_id": "runtime-b0b5049389e52df4", + "start_frame_id": 220, + "start_pts_ns": 5306725932027, + "status": "aborted" + } + } + ], + "shutdown": { + "exit_code": 0, + "forced": false + } +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T085500Z-synthetic-output.json b/evidence/local-20261009-capability-audit/eleven/20261009T085500Z-synthetic-output.json new file mode 100644 index 00000000..564125fe --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T085500Z-synthetic-output.json @@ -0,0 +1,102 @@ +{ + "phase": "synthetic-local-only", + "started": 1791535463.2837946, + "checks": { + "all_real_audio_muted": [ + { + "name": "PulsarDesktopAudio", + "muted": true + } + ], + "synthetic_program_items_only": true, + "program": { + "currentProgramSceneName": "AuditSynthetic", + "currentProgramSceneUuid": "55df826a-7b03-4762-96bd-e9c55eabb14f", + "sceneName": "AuditSynthetic", + "sceneUuid": "55df826a-7b03-4762-96bd-e9c55eabb14f" + }, + "video": { + "baseHeight": 1080, + "baseWidth": 1920, + "fpsDenominator": 1, + "fpsNumerator": 60, + "outputHeight": 1080, + "outputWidth": 1920 + }, + "encoded_video_and_generated_audio": true + }, + "before": { + "activeFps": 60.0000024000001, + "availableDiskSpace": 123850.46875, + "averageFrameRenderTime": 0.323252, + "cpuUsage": 1.7359324111058707, + "memoryUsage": 93.31640625, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 89, + "webSocketSessionIncomingMessages": 17, + "webSocketSessionOutgoingMessages": 31 + }, + "during": { + "activeFps": 60.0000024000001, + "availableDiskSpace": 123847.09375, + "averageFrameRenderTime": 0.4294, + "cpuUsage": 1.437654290001127, + "memoryUsage": 208.4921875, + "outputSkippedFrames": 0, + "outputTotalFrames": 240, + "renderSkippedFrames": 0, + "renderTotalFrames": 337, + "webSocketSessionIncomingMessages": 19, + "webSocketSessionOutgoingMessages": 35 + }, + "media": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\synthetic-recordings\\pulsar-20261009-104425.mp4", + "bytes": 3100119, + "streams": [ + { + "codec_type": "video", + "codec_name": "h264", + "width": 1920, + "height": 1080, + "r_frame_rate": "60/1", + "sample_rate": null, + "channels": null, + "duration": "4.016667", + "nb_frames": "241" + }, + { + "codec_type": "audio", + "codec_name": "aac", + "width": null, + "height": null, + "r_frame_rate": "0/0", + "sample_rate": "48000", + "channels": 2, + "duration": "4.010667", + "nb_frames": "188" + } + ], + "duration": "4.016667", + "audio_rms": 2118.2212489787275, + "audio_440hz_amplitude": 2995.262499024063, + "video_average_rgb": [ + 255, + 0, + 254 + ], + "exit_code": 0, + "forced_kill": false, + "elapsed_s": 7.918874740600586, + "encoder_markers": [ + "2026-10-09T08:44:24Z INFO OMzNi3HBnFiwJf9u pulsar-frontend-stub | video encoder allocated: family=nvenc id=jim_nvenc, 6000 kbps (stable ProgramView binding follows)" + ], + "shutdown_markers": [ + "PULSAR_SHUTDOWN_CONTROL event=ready id=audit-synthetic-20261009 mechanism=inherited_event", + "PULSAR_SHUTDOWN_CONTROL event=signaled id=audit-synthetic-20261009 mechanism=inherited_event", + "2026-10-09T08:44:30Z INFO OMzNi3HBnFiwJf9u libobs | PULSAR_CEF_SHUTDOWN event=pre_obs_shutdown_begin", + "2026-10-09T08:44:30Z INFO OMzNi3HBnFiwJf9u libobs | PULSAR_CEF_SHUTDOWN event=pre_obs_shutdown_complete reason=cef_not_running", + "PULSAR_RUNTIME_INSTANCE runtime_dir_lease=released id=audit-synthetic-20261009 dir=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\runtimes\\audit-synthetic-20261009 authority=Local\\Pulsar.Authority.31eb85ecb5c9d1cf metadata=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\authorities\\session-1\\31eb85ecb5c9d1cf.lock", + "PULSAR_RUNTIME_INSTANCE lease=released id=audit-synthetic-20261009 dir=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\runtimes\\audit-synthetic-20261009 authority=Local\\Pulsar.Runtime.audit-synthetic-20261009 metadata=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\authorities\\session-1\\b8fa9ec736f9a3bb.lock" + ] +} \ No newline at end of file diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T085600Z-cef-shutdown.json b/evidence/local-20261009-capability-audit/eleven/20261009T085600Z-cef-shutdown.json new file mode 100644 index 00000000..4f674ba0 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T085600Z-cef-shutdown.json @@ -0,0 +1,9 @@ +{ + "started": 1791536046.3661785, + "real_audio_muted": [ + "PulsarDesktopAudio" + ], + "passed": true, + "exit": 0, + "elapsed_s": 6.272343635559082 +} \ No newline at end of file diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T090000Z-audit-checkpoint.json b/evidence/local-20261009-capability-audit/eleven/20261009T090000Z-audit-checkpoint.json new file mode 100644 index 00000000..dd9b7ea8 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T090000Z-audit-checkpoint.json @@ -0,0 +1,20 @@ +{ + "owner": "Eleven", + "mandate": "Audit, sans ADR, sans agent, sans Computer Use. Aucun correctif produit dans cet incrément.", + "path": "D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261009-capability-audit", + "branch": "eleven/local-20261009-capability-audit", + "HEAD": "3e77323d1915e7ff09cc799594474ed56d758c2c", + "files": [ + "?? .cache/", + "?? evidence/local-20261009-capability-audit/" + ], + "validation": { + "headerProbes": 3, + "configurableTransition": "passed", + "previewContinuity": "passed", + "syntheticNVENC": "passed", + "CEFShutdown": "passed" + }, + "remaining": "Correctifs non implémentés. Preuves et copies sources conservées dans les worktrees attribués, sans intégration Git. Préserver les sessions actives et ces preuves non intégrées.", + "aggregate": "D:\\Documents\\Zab\\Prism\\.worktrees\\eleven-local-20261009-capability-audit\\evidence\\local-20261009-capability-audit\\eleven\\20261009T090000Z-audit-report.json" +} \ No newline at end of file diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T090000Z-native-contracts.json b/evidence/local-20261009-capability-audit/eleven/20261009T090000Z-native-contracts.json new file mode 100644 index 00000000..5de6120b --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T090000Z-native-contracts.json @@ -0,0 +1,20 @@ +[ + { + "name": "pulsar-media-transition-config-probe.exe", + "exit": 0, + "output": "media configuration: valid WebM/MP4 accepted; 22 invalid cases rejected", + "sha256": "8DC71096E6623FABB5ABC28B5334C1EA702A66F884C02E0E53D1479E22E12931" + }, + { + "name": "pulsar-transition-controller-probe.exe", + "exit": 0, + "output": "", + "sha256": "8588983884425DDB0BA53DFEA7739E21A19EB7C22BA6DFF01855FE3F82492F5E" + }, + { + "name": "rtwq-lifecycle-test.exe", + "exit": 0, + "output": "PASS: reproduced uninitialized Capture queue (0xC00D3E85)\nPASS: three host lifecycles and 36 Capture queue recreations\nPASS: independent platform owner preserved; no startup reference leaked", + "sha256": "65CBF67DAD9951505676C36191ED32885366F3389C672E09667E5217DA93F7F6" + } +] diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T090100Z-source-manifest.json b/evidence/local-20261009-capability-audit/eleven/20261009T090100Z-source-manifest.json new file mode 100644 index 00000000..d3f41b77 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T090100Z-source-manifest.json @@ -0,0 +1,1090 @@ +{ + "source": "D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261009-capability-audit", + "at": "2026-10-09T09:01:46.957Z", + "files": [ + { + "path": "plugins/pulsar-browser/browser-app.cpp", + "sha256": "9e906228a8555b03513141f4be3c45177093b746b4ba1ed6348e8ed0e926d473" + }, + { + "path": "plugins/pulsar-browser/browser-app.hpp", + "sha256": "8711d48139ff222fcfafcf135316cad2d278c4902a69fe71d16c859e2f640604" + }, + { + "path": "plugins/pulsar-browser/browser-audio-callback-gate.hpp", + "sha256": "862cc27b78aa8f904da2ebeffb96e19a6103a6a61df71082bc99172153cd4bd5" + }, + { + "path": "plugins/pulsar-browser/browser-client.cpp", + "sha256": "63285daa3e93beed81ad0861b8866e5d7d6876549ec9f6f1f7d142ca8e189be0" + }, + { + "path": "plugins/pulsar-browser/browser-client.hpp", + "sha256": "bdd6e1aa69878eb95b36e3e70a968cca437bdc84cc7cda451b659e177dc72d78" + }, + { + "path": "plugins/pulsar-browser/browser-render-callback-gate.hpp", + "sha256": "f95c5abaf34bad4835f92c2fa2f88c39acf27e841e1342e877a1dd15e0288ca4" + }, + { + "path": "plugins/pulsar-browser/browser-scheme.cpp", + "sha256": "6e48a4d5ad69eef8d2b8b23386f7f7cbc1b2f7edc73fb8aed91a6d49e2ac0679" + }, + { + "path": "plugins/pulsar-browser/browser-scheme.hpp", + "sha256": "ad723a25115f82cf383b045ae8b84ec9c84a207837a61d7d0b7972da00012829" + }, + { + "path": "plugins/pulsar-browser/browser-source-task-state.hpp", + "sha256": "f4b2d099f89ac192d40a4e8ee76696b6d451bf7e898a3d225bffb388413a7bbc" + }, + { + "path": "plugins/pulsar-browser/browser-version.h", + "sha256": "b70741e9302f5d9c284e1a6e1abc70273d1b873aa518d36d959eb71f1d481ddd" + }, + { + "path": "plugins/pulsar-browser/cef-headers.hpp", + "sha256": "c2d488d762fc7993e4d2815bf7c6dd1c82b832fe39f789a50960f1f242f84beb" + }, + { + "path": "plugins/pulsar-browser/deps/base64/base64.cpp", + "sha256": "6cb0fd9f94a289c11380e8bb27f4865cfc96e8e85603eab5feb475b413cf1987" + }, + { + "path": "plugins/pulsar-browser/deps/base64/base64.hpp", + "sha256": "4dfd140e94c2e5a1ffea71ff9d683800b41e4c2f27f0eaa35b050c41e09c62f0" + }, + { + "path": "plugins/pulsar-browser/deps/ip-string-posix.cpp", + "sha256": "9890e27c50445b3efa91a104f60332152bf6ffebd395769ca228597578315185" + }, + { + "path": "plugins/pulsar-browser/deps/ip-string-windows.cpp", + "sha256": "6a4377b0a9f1fd3f727f5a7029a2e04cc556f4351891749d7081031023332397" + }, + { + "path": "plugins/pulsar-browser/deps/ip-string.hpp", + "sha256": "3225418c0af362d6ffbc8fffb03ee07f39440e5257cf0f9dbc1bfdb69ef97ced" + }, + { + "path": "plugins/pulsar-browser/deps/signal-restore.cpp", + "sha256": "a198b701a4aa2b5d47ffe0a1ce92df4513182c823d979cfedc42ce1cde97adaa" + }, + { + "path": "plugins/pulsar-browser/deps/signal-restore.hpp", + "sha256": "c3a0bfa6b6dbc71bf41b3d0d8bdd411293bbafb0b8e7e672adff58f1fa3047cb" + }, + { + "path": "plugins/pulsar-browser/deps/wide-string.cpp", + "sha256": "90c3f177898066eb7d4c29a144a56ce1fbd4a264af256d2be25fe55f292651ba" + }, + { + "path": "plugins/pulsar-browser/deps/wide-string.hpp", + "sha256": "13801c3a6f75affc1e6331a082481a462d5b64773e8d5a7301b927b9109bffb7" + }, + { + "path": "plugins/pulsar-browser/drm-format.cpp", + "sha256": "c2f0fa3aa250009f40ae4e8a430bc86b89102b3f135761a09f8ab55e78be3fc9" + }, + { + "path": "plugins/pulsar-browser/drm-format.hpp", + "sha256": "acb74cb4dad470fc52f29a04ef8b59b4c76700780a46cf5a176ea8ae507c516f" + }, + { + "path": "plugins/pulsar-browser/linux-keyboard-helpers.hpp", + "sha256": "628ed35587fff8bc09740375f2e83a94b586fe7506de70a1b91079548c872996" + }, + { + "path": "plugins/pulsar-browser/obs-browser-page/obs-browser-page-main.cpp", + "sha256": "b87d0a00b8d4da1b09232f83ab9b752b7b44648042ae4880dcfa5d2d79f7b278" + }, + { + "path": "plugins/pulsar-browser/obs-browser-plugin.cpp", + "sha256": "686a0823b79f0c26a3ba21898579f7029596e11c19daaebac8971c96086b07a4" + }, + { + "path": "plugins/pulsar-browser/obs-browser-source.cpp", + "sha256": "00fad67585f3242b29dbc616e98f5b992eaa45e4951f8d25444fa0061f362da6" + }, + { + "path": "plugins/pulsar-browser/obs-browser-source.hpp", + "sha256": "ee60fdd0285fdca9b45d31d0c3ab3510c3d22b2c399ee10c254b81c29bcc7224" + }, + { + "path": "plugins/pulsar-browser/panel/browser-panel-client.cpp", + "sha256": "b1d43734c5029c86a4fe7351c6f036f794807b9a1efdcadd92079c6e40083345" + }, + { + "path": "plugins/pulsar-browser/panel/browser-panel-client.hpp", + "sha256": "ad4855f7712777b56169178473d786bfb95a24fb69772096efd058b56a0bebdd" + }, + { + "path": "plugins/pulsar-browser/panel/browser-panel-internal.hpp", + "sha256": "92cfde731699304cc72c60fd7a88bb45dc9398031302d83dbf2b73cf53d2b02b" + }, + { + "path": "plugins/pulsar-browser/panel/browser-panel.cpp", + "sha256": "e1cbc6168abd3ec655346950ccb667c20f1ee7aaacb1a9135dc89d3fa5e91597" + }, + { + "path": "plugins/pulsar-browser/panel/browser-panel.hpp", + "sha256": "d88f281decbd66a58644e27078531a5a514a75aada4fcc154ad315bb5823890d" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-dual-lane-config.h", + "sha256": "512afac024c8bfef203c54b981b5ee91c01bb74f6a661c83bb25b366084addb4" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-dual-lane-control.h", + "sha256": "a5035b523b7b9355f4a7b52a56a3ad4a3f7dab75c48dc56e4ebe83c159953258" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-frontend-stub.h", + "sha256": "6e99961e43dbce2a182b6de922b0694b5039a19c23aeecf59f28976e8e193fdb" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-media-transition.h", + "sha256": "162a318abaaa3d948526681fa5cbcc62e6540ef9d23463902c7f00dd06e65ce9" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-preview-frame.h", + "sha256": "dd419b56cbe401510873cdde83191436703cf0b97a4128e800310bfb859673d3" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-program-audio.h", + "sha256": "eb72ab4fcee66d338094ef4692078cd4bc218274ba63ad0a2fd9feb4dbac4cc4" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-runtime-telemetry-abi.h", + "sha256": "8d7335b178e841abb9de49b43268111ca144d018199fa3d04d79f45997d8ece3" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-runtime-telemetry-signals.h", + "sha256": "29a0401b057ec128c36332d1d2bde7eeb70609820a3378b72009646b71398db4" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-runtime-telemetry.h", + "sha256": "76f0634b1654a9987d1f257e425c331c018e38c4604f00ff7971682dae9b589a" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-stream-egress.h", + "sha256": "f42a73ce109ec2475cd5dc13bd1b3db30bcc016f85a216715ff1ddf49edb54e1" + }, + { + "path": "plugins/pulsar-frontend-stub/include/pulsar-transition-controller.h", + "sha256": "967278eb0c5e61b17e0b30730d45475fde5e8dac09e65a93456aca85bc816a8f" + }, + { + "path": "plugins/pulsar-frontend-stub/src/pulsar-frontend-stub.cpp", + "sha256": "6798dcf360c088203a48d22036769ae549ed7b7b70f20e3e1724bb7b1f09500a" + }, + { + "path": "plugins/pulsar-frontend-stub/src/pulsar-media-transition-config.cpp", + "sha256": "0aed92066ad176c18b305ed42925802af0d94777d96911e7ba6d0196097e1310" + }, + { + "path": "plugins/pulsar-frontend-stub/src/pulsar-media-transition.cpp", + "sha256": "da224be967ce60142bd312a2d25440b3c0a4b5c93c21895454b0c5f64bcd49f3" + }, + { + "path": "plugins/pulsar-frontend-stub/src/pulsar-preview-frame.cpp", + "sha256": "b214bbff64cec62befe81a1c41a33eddabb7b17ec94bca7a5849bfe2a754ab7d" + }, + { + "path": "plugins/pulsar-headless/dir-hardening.cpp", + "sha256": "532a78f34d45f492ca57b246e71a6c0106e41f521508123765a4a6aa3f2b011b" + }, + { + "path": "plugins/pulsar-headless/dir-hardening.h", + "sha256": "8c8f5be91c0ebcba68ef9925e4491919ee66a86d7f394a177295581fdeb91dfd" + }, + { + "path": "plugins/pulsar-headless/log-handler.cpp", + "sha256": "a40bd8c7c1f649a8a9009c911208e413a19716ebcf720d92e4fc91585da168f5" + }, + { + "path": "plugins/pulsar-headless/log-handler.h", + "sha256": "3a7a7c09a2924a1c3f37e98702843e50f3d65dbed87227c50482b31adb9260ed" + }, + { + "path": "plugins/pulsar-headless/main.cpp", + "sha256": "e96331c0fd7e559c6e954e07f300c9cc61d1901a07808c44bd4a4722efca07a4" + }, + { + "path": "plugins/pulsar-headless/realtime-work-queue.cpp", + "sha256": "38bc5bb7d8119ba1e6cdc6198ffd70b8b2a9a678d17bdf1ef97aca2f8deda47a" + }, + { + "path": "plugins/pulsar-headless/realtime-work-queue.h", + "sha256": "0ca1d4f232a3308cd52d77e65f195b342e706ae05635004e3d2224cb3ec30154" + }, + { + "path": "plugins/pulsar-headless/runtime-identity.cpp", + "sha256": "1a2610611827553026c2cf420d731b74d2b18d3eb81c855243f8c3e9f748e641" + }, + { + "path": "plugins/pulsar-headless/runtime-identity.h", + "sha256": "5df36702d236b2f13dbaff774e6266b08e9a68636466578a03f746b07f3f9228" + }, + { + "path": "plugins/pulsar-multi-stream/src/plugin-main.cpp", + "sha256": "5c7f3f7b60ae153e5f1be4d2e7be919db3cabbb8a1bf49233d66dd6ce4253324" + }, + { + "path": "plugins/pulsar-nv-secure-load/pulsar-nv-secure-load.h", + "sha256": "5f71bf59896fa76aa6532f7dcf777e6e08b1982f8527839abdfcae9711ebc7d3" + }, + { + "path": "plugins/pulsar-output-classify/pulsar-output-classify.h", + "sha256": "84ccd7c1dc6007ab08260b7c52f01dc9d4e34ae5639ab6a06a040082cdcb9041" + }, + { + "path": "plugins/pulsar-scene-source/src/plugin-main.cpp", + "sha256": "65b2de7834307aabe4bc2e85e595e8b753593d7b8b30b70067c6cce0aba071d5" + }, + { + "path": "plugins/pulsar-websocket/lib/obs-websocket-api.h", + "sha256": "c29e8e38ee66c36db79cae86217ab0f270aad39f97e82090809d087c281b7948" + }, + { + "path": "plugins/pulsar-websocket/src/Config.cpp", + "sha256": "8971dc9ebb21daffa92353238d474e619ac2d2462312161b6baa9280932bb5dc" + }, + { + "path": "plugins/pulsar-websocket/src/Config.h", + "sha256": "f64b54b6d72cb2c27071d291e38bc0a6c1ff9d7303f512049361b8c0c683e8b9" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler.cpp", + "sha256": "21a99abfe4acd05638dcf319d8b35fb62aab51730e2d000f4ca250d4bdc6a13b" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler.h", + "sha256": "db620d2cbc98fa7cec709e8db178c5847a1ec23d31ac1a493359e459b6df752d" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_Canvases.cpp", + "sha256": "3e9485f9956b5978f6b4b861488605d113b01665fa5c3dd45b17586832597564" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_Config.cpp", + "sha256": "3235ceef1e1149947a42bbf0c6a50222632045716c5b9b2ad69ac6e6a187e99e" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_Filters.cpp", + "sha256": "7dbc95a48f4db9c0b8eadb665656c3508e89ee1cfc3700d07e01991eb3a7eb45" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_General.cpp", + "sha256": "3cbbc47c24610d2c045a0ab5007d3c7352bbb28296693b66fef52bbc3a4ecb3c" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_Inputs.cpp", + "sha256": "bee09a98bdc86ede0527d9b9810d934141741e91e8251d677ad18566806f4312" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_MediaInputs.cpp", + "sha256": "f807093d2a45001a94ab08d8177ac213d40710260da7b2324f8aa1d3387eabdd" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_Outputs.cpp", + "sha256": "e11861bcfb64f9a831abc03a278d9c835b349165298cccac2519469e968ec90b" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_SceneItems.cpp", + "sha256": "7b17ba656b7f7de4822dbdcb4fbce8e721268b61749d8efd7e592eff954d84ac" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_Scenes.cpp", + "sha256": "95704c6c51faee8d8be8879e4ea4a990402bb69981814bcdcc78179f8b6dadf3" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_Transitions.cpp", + "sha256": "f38617cae6ff61beb3386372a7cdf570f411fc60274ca0c9443a043dd8493b03" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/EventHandler_Ui.cpp", + "sha256": "c46880dc6457843a9fe4a545a4e7b5ba86c99ea672040b9a5eac7539c93a8d23" + }, + { + "path": "plugins/pulsar-websocket/src/eventhandler/types/EventSubscription.h", + "sha256": "581ebca96ffc157a9738c250c823182d41e9ba7fb2dff62127f3bf35ca81eaf1" + }, + { + "path": "plugins/pulsar-websocket/src/obs-websocket.cpp", + "sha256": "a7218ae8699ad65214ab2d77726afa73cebabd6ec85f1dceb9a0700a5e67382b" + }, + { + "path": "plugins/pulsar-websocket/src/obs-websocket.h", + "sha256": "08527e853bede58ae93cd9cf4506af43d4e8de21dbebc1cb60cbcb9c32fc71dd" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/OutputEffect.h", + "sha256": "165082862e6012dc2606d56d4ce8708ccc11437bbc36855e48eb8aa85c127ee2" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestBatchHandler.cpp", + "sha256": "e39a8e1c3a3e3b0f42323f71cac38ec8d78d2b28f491c80483cac0126dfb509a" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestBatchHandler.h", + "sha256": "cdbe5694df4a5a5c38fd3b9029d508682e7c40eae305f1a77925e5d41cfdda0d" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler.cpp", + "sha256": "5ca45e2f605b40a6c016c6ea30ef0080e306f420d88fab2dec597e08ccd4a630" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler.h", + "sha256": "3b9b0b377e93b352b8cea2f6849c5912e319c715e2d11b8f9a1cd85c66808695" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Canvases.cpp", + "sha256": "87331abcfc2ca47fd5ea4123206ea19a0745d238d3acec0689f59a427a8ffd49" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Config.cpp", + "sha256": "7b89e5463ec945efe44a5b515b5ddf7d47be640f0cf2a5b4ba7f5f575794dbb3" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Filters.cpp", + "sha256": "1fc20ce9b351ba56b32435dcac4fb7fd1dffa0659ef879d4da7941c4f79d9e72" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_General.cpp", + "sha256": "f0dc3bc65e526e906b6f2257e5215884a9a729d869f0cc8785afe624cc84b874" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Inputs.cpp", + "sha256": "b73ddf59bb3a1339fb77ca47c543a49c91cfd93d1a8ad835e9c396eaf117d9b1" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_MediaInputs.cpp", + "sha256": "94c84dff87e42c3e220db1effb6266c0cc40c5aba86b328fb0b8f0f1d82a5420" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Outputs.cpp", + "sha256": "3f1bd5b75bb55ddc9460b161bd0e98d3058eaf572979ff2aee6876e55c9e7d94" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Record.cpp", + "sha256": "7837355858c9eeda748ce70e5958efb7f23ed11eb370cd6e5d98f2e630bc177e" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_SceneItems.cpp", + "sha256": "d553af34bbd5c8833730d595d4b95432a0e1b05242699fd4aca6df4286146175" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Scenes.cpp", + "sha256": "bc8adf6184494148aaa29494aa16710f8b47a71196bc10c4e0274ef89cc5bddc" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Sources.cpp", + "sha256": "de363eda4cbd4cfccb8fc9b6832b5743ee430cda99faa01cc8a11a7dd433eec9" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Stream.cpp", + "sha256": "71f8d9dd73052c5bae364644b1dce00e142cd581923edba40dee000a94ef2779" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Transitions.cpp", + "sha256": "e81819711c0986520fd4f25333991dde5e1e41db01d17536dd86812d485fa435" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/RequestHandler_Ui.cpp", + "sha256": "2a5ebc1ca857f08f074c39e936854f967cda799712ea5040296c9ab5038a8982" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/rpc/Request.cpp", + "sha256": "c67d9b618ff142f66055a109ecbac4829a1739e0588da910fc3d6effa1223417" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/rpc/Request.h", + "sha256": "a7c231301894923adeb91691f59548fad147de219ad47451a6d26821d8dbec86" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/rpc/RequestBatchRequest.cpp", + "sha256": "2eeb6c90ff9357904fb7244ca398165de94cd95544c492a02382cc8374507be2" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/rpc/RequestBatchRequest.h", + "sha256": "ba6c7b300d6f2491a4939f472e5d19b5f29867751a00464452876d44a293369a" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/rpc/RequestResult.cpp", + "sha256": "5228f87b820e8919ca6efeb3cbb3ff9f04676e4a414e17c087518becbfe5bdb1" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/rpc/RequestResult.h", + "sha256": "1c3259aaa1c3075f53884492a07d700af8eb025be10bc151d07cc5bc80316ef4" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/types/RequestBatchExecutionType.h", + "sha256": "57759dd27c2ab75559bec62a050677d91e69226e2235eaeb8edcfe46497857a4" + }, + { + "path": "plugins/pulsar-websocket/src/requesthandler/types/RequestStatus.h", + "sha256": "82c876cf182705029dae6e06449a3d7ed696812cfeec5c33f7e5393590d22227" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Compat.cpp", + "sha256": "fefdba925f9de246af29864a6580367c92ceac8a6bc830f680ddd08de7b4125d" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Compat.h", + "sha256": "8351e922511da9b8287853d39ae34ff89bc80ca74546f127e137ff7245c16b36" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Crypto.cpp", + "sha256": "54e06acdb7cfce20ae73ce19f25b2afd341ab158665cbbc1127f706089a3e55d" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Crypto.h", + "sha256": "157f2429ab5b1567f31f4592bed55ead6b7bd6718bec276d36bae818b4e87b3f" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Json.cpp", + "sha256": "e40f65258346441c4435928e46c929ba20b8e0eabcc9a8334f6edfa538fadc0f" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Json.h", + "sha256": "7a978f7f0ac1a76de5e30d82dcedf99e30decb0a14c49c21d4265ee8c3818e10" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs.cpp", + "sha256": "c46a4c14bb6da0a80c1179371b200b2a17cdfa34243069193e11f01ec9f02a1f" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs.h", + "sha256": "0d6367a673754fb2e194e272fbda2cafe98af6f0909e72587a4c82e784fe58d6" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs_ActionHelper.cpp", + "sha256": "5a538c925fbb4ec7282189df72108e473622515fb748f75912b1fac77f3f00c8" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs_ArrayHelper.cpp", + "sha256": "7b7d6d85fdec784696222658de800d190bb6de6e8ef213fad60f339c42cd9cb0" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs_NumberHelper.cpp", + "sha256": "262999dc97e2a36b6a24bb1536c7990db9cb70e57529acb0cae2746e8b8267ba" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs_ObjectHelper.cpp", + "sha256": "a707d7c9de6dee20db13271edc3da7cccfac25b42a501db65199cd19ca51ee91" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs_OutputHelper.cpp", + "sha256": "ade1bfff828b782a52ed95e73e1d94ca0807e7a901f71cb2a192d321b1a97fc2" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs_SearchHelper.cpp", + "sha256": "29647fce7f13e2c5a00af7e276aeaeef921b4760b2f3dcc826bb608c58338d73" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs_StringHelper.cpp", + "sha256": "02778c0a7cd24f3994e902ee8c2d36fd109f930f536c95897964d8104e3c2452" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs_VolumeMeter.cpp", + "sha256": "9b27c3d1656b3b09e8ae2aa654a21b0689873adb0fc0e70adc276751d5734f4b" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs_VolumeMeter.h", + "sha256": "2abc6242ebe8e6bc7da11be4940bf7de96e7586735a99a2273798952aee23c6d" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Obs_VolumeMeter_Helpers.h", + "sha256": "4b1f9afb2e0855337f992547f9de7de520d28e60c2b1455d66dcb25caf0c4f77" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Platform.cpp", + "sha256": "7e24a34fdf8c59e3aa791f2bc6e906411f5a22e1f8d67b84a5761691800e1e9c" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Platform.h", + "sha256": "6969726dc821ce59fdca7eb37b312e0c844f160a5a5c2ba5f7053a6eb0b3f0e0" + }, + { + "path": "plugins/pulsar-websocket/src/utils/Utils.h", + "sha256": "5f76ab368f09792b5a3b57d09cf3ba34ee1397d201d6822ed342b143eb13859c" + }, + { + "path": "plugins/pulsar-websocket/src/WebSocketApi.cpp", + "sha256": "ffc2e70265b1e3699a8d858bd5ddf635d444115d2b404cac1403e8a9b5c1d8c9" + }, + { + "path": "plugins/pulsar-websocket/src/WebSocketApi.h", + "sha256": "02843d93afa31ec5c327924823d7be946ee687aebc0603a76bf41f72dba346b4" + }, + { + "path": "plugins/pulsar-websocket/src/websocketserver/rpc/WebSocketSession.h", + "sha256": "022e3bd257607bc09aedfd26c8cc96a51d517db356d8d7fbf8a21e435b97a02c" + }, + { + "path": "plugins/pulsar-websocket/src/websocketserver/types/WebSocketCloseCode.h", + "sha256": "682855627fdc37eec80c406497aff7d47c0f9cdd951d6fe24a1d2c66ca44bd8b" + }, + { + "path": "plugins/pulsar-websocket/src/websocketserver/types/WebSocketOpCode.h", + "sha256": "9b5911e9ae965b0ca9c9d2db39861af2e153c3c2c1738f1ac15aa4f9c1f3be8a" + }, + { + "path": "plugins/pulsar-websocket/src/websocketserver/WebSocketServer.cpp", + "sha256": "37c0edca414d742704388b55b1f78c8ed72306f049602c1abe98d92733ea90a1" + }, + { + "path": "plugins/pulsar-websocket/src/websocketserver/WebSocketServer.h", + "sha256": "2f3649539843631b8228735f6d3d68782aa8d25c80f16374b84cef259cdd18e4" + }, + { + "path": "plugins/pulsar-websocket/src/websocketserver/WebSocketServer_Protocol.cpp", + "sha256": "00d026bc57c73978254fcd17dc5ff2a906cc25b1afb9a25ddb165849ebe06f6b" + }, + { + "path": "scripts/analyze-telemetry-signals.py", + "sha256": "46607953e356cc685885ec3e23181192f4b00bbd3e8c0b20dc959d40f383760b" + }, + { + "path": "scripts/assets/stinger-demo.manifest.json", + "sha256": "e1bd8e0cfbabb3e55494c000a9ef24eba4c5a6a1aa3a3a8217c107d5ff116596" + }, + { + "path": "scripts/bench-media-source-load.py", + "sha256": "8d7b12790b4ab2dd551fd9e88ad58b49ccf5f08f05043cd5611ba0315f4993ea" + }, + { + "path": "scripts/check-nv-filters-packaging.py", + "sha256": "8cd25120288951e629e02b0b4947bbf4f9e83a6a3ee5b17159e246db3a259024" + }, + { + "path": "scripts/check-nvenc-preset-contract.py", + "sha256": "f61f59153c579fd842e699148a68569aa76768b9a325008fa8ce6f76df10f1d4" + }, + { + "path": "scripts/check-qsv-preset-contract.py", + "sha256": "5ddc75163b6fe3443f3f984dd4c60fa010edde22d41637a464133308c26eb5c0" + }, + { + "path": "scripts/check-webpage-control-level.py", + "sha256": "99b910f87f7567f2c06e8c72d93547502230873ee102287c2e6aeccf5b3ddc03" + }, + { + "path": "scripts/ci-native-cache.py", + "sha256": "4afd780a56d30048b29481532cc2f7051a178f506985e27f58435ea8be009cb0" + }, + { + "path": "scripts/contracts/capability-coverage.json", + "sha256": "c1229f8ffab6a3e55ae0cf84fcff18ad3436311301e81cd5162d20e570897576" + }, + { + "path": "scripts/contracts/scene_control/fixtures/malicious.json", + "sha256": "2cdf3a11e1a999d5b75e5d709313cae0973bbb08a4bb8fba1d4c7b7af7e11996" + }, + { + "path": "scripts/contracts/scene_control/fixtures/valid.json", + "sha256": "fa281a4bc2de1c7a5bb345bc84a38da7fe34ea253aab1b41d9a3c4235b04f2e4" + }, + { + "path": "scripts/contracts/scene_control/test_252_preview_audio_descope_contract.py", + "sha256": "64027f786a2415444b1bf470a64df192b23733af6a0dc75879bcef8ae1c87cab" + }, + { + "path": "scripts/contracts/scene_control/test_dual_lane_contract.py", + "sha256": "b6c5e1f8a7987a7d8566af5e18c5416fb704a790e9b08fd0607730d822c08d71" + }, + { + "path": "scripts/contracts/scene_control/test_program_audio_contract.py", + "sha256": "cd3c32a1854821fd2b31c8689593942d0113dee984eebfe5478d98eafdbb02be" + }, + { + "path": "scripts/contracts/scene_control/test_scene_control_contract.py", + "sha256": "cecd76f0f98f55e55ce24d78c36b17acec97fc4ef891e48805a8216b66c5377a" + }, + { + "path": "scripts/contracts/scene_control/__init__.py", + "sha256": "96fd14190d4ef31c6980a6d85dd7ff82dcc9412c8e672f347c87a8f7bc2e1e60" + }, + { + "path": "scripts/contracts/scene_switch_v1/fixtures/examples.json", + "sha256": "d507f72103027d4e43e7073e3b0f139c9959b9347a95cb43162998e1674b284a" + }, + { + "path": "scripts/contracts/scene_switch_v1/schema.json", + "sha256": "518f2af390cecbd9a5403f3e77b5c26855e5762243b4bfdf859b2e2f45234525" + }, + { + "path": "scripts/contracts/scene_switch_v1/test_draft202012_schema.py", + "sha256": "af9648f7932361d6d78d707e061161b3459f20d951f85d80416112d497549bf5" + }, + { + "path": "scripts/contracts/scene_switch_v1/test_low_latency_interleave_contract.py", + "sha256": "25d9a49f8fd73566098cbfaa0775c12f84809fc1ec6bbd57948ec55506f9ac82" + }, + { + "path": "scripts/contracts/scene_switch_v1/test_probe247_evidence.py", + "sha256": "3de62816a0584b9b739cd1d6a25ab96af5190a5a4ec0aa7158c075e8ae3de886" + }, + { + "path": "scripts/contracts/scene_switch_v1/test_runtime_telemetry_producer_contract.py", + "sha256": "0a144b24ad4c6cb68ac6c98a736690e71c5aa304abe270ffc7daa5ebe337eb2e" + }, + { + "path": "scripts/contracts/scene_switch_v1/test_runtime_transport_contract.py", + "sha256": "3c87a737e19535d4a85252ef8d6a0fd201effd466380e6374ddd1e7a9444d66d" + }, + { + "path": "scripts/contracts/scene_switch_v1/test_scene_switch_contract.py", + "sha256": "a8cfc342ba22f68e25f77a77e8d831944a1539b0bad6677f857ce9918dc4c64d" + }, + { + "path": "scripts/contracts/scene_switch_v1/__init__.py", + "sha256": "ed0551c91721ec5e6ab76806c234eb2567a168e783290d4408dcd0e87aa68707" + }, + { + "path": "scripts/fixtures/m10-orion-scene.lsml.json", + "sha256": "12f4a6848696c0ef22463477314d7a5d54a5100baf4e00d42014fbbb8703c09b" + }, + { + "path": "scripts/live-test/vendor/babel.min.js", + "sha256": "2623a9e22809915ce789b4461154e277ddce520d5a4320c14d44332a5d0dcea0" + }, + { + "path": "scripts/live-test/vendor/react-dom.production.min.js", + "sha256": "35f4f974f4b2bcd44da73963347f8952e341f83909e4498227d4e26b98f66f0d" + }, + { + "path": "scripts/live-test/vendor/react.production.min.js", + "sha256": "d949f1c3687aedadcedac85261865f29b17cd273997e7f6b2bfc53b2f9d4c4dd" + }, + { + "path": "scripts/m10_orion_standin.py", + "sha256": "d9957a112525d7d929f33df599b618149b1aeeee93f6421fc9320f83ee5d7a37" + }, + { + "path": "scripts/m10_setup.py", + "sha256": "127e9e5a84d7d603634861e0b3e700bb2e618fa94beef66579e0645485069298" + }, + { + "path": "scripts/prepare-pages-proof.py", + "sha256": "cec24401a17ee9c9d6b357a616813ee6e101e816b374da1cf3927d17dbf488e3" + }, + { + "path": "scripts/probe-247-runtime.py", + "sha256": "c802c76c56a90f84c83c1cad5fc73d0a8a4039b2126106df7c724062337064ef" + }, + { + "path": "scripts/probe-adaptive.py", + "sha256": "64967aa371dc73909d8b854f03c3232bcb9e84f5603372da56b0f19d10aa7795" + }, + { + "path": "scripts/probe-audio-multitrack.py", + "sha256": "8c29b3279c3b53049810e96515c0e89846655108ce8003edd4693a57c62826d9" + }, + { + "path": "scripts/probe-audio-tracks-oracle.py", + "sha256": "58c7ced80413ff3e939454afd17c2bd1d279975374a968f5f8958ad4231e84e6" + }, + { + "path": "scripts/probe-av-flash-click.py", + "sha256": "50293b94e3d6784119d0212b941ffaee03fc21f0a0bc8beec13c6338a8a98a41" + }, + { + "path": "scripts/probe-browser-m3.py", + "sha256": "6973659df3acdcab859076c58649c8928015b669a282d52fce2ea8762e87ee08" + }, + { + "path": "scripts/probe-capability-contract.py", + "sha256": "b3bda83a300068bc22a9ef128ea3d14e1fe2e69f8b9cdac0a7294056d1e6d10d" + }, + { + "path": "scripts/probe-configurable-transition.py", + "sha256": "33fbadd99b86243cca69675655ed7b00c13b69dc02d406267c179387e46f7d8f" + }, + { + "path": "scripts/probe-dual-lane-rollback.py", + "sha256": "6b95846e1bd40b4a685a535779566b4f3516f84b3ce3a545ca4ede2e09552b7c" + }, + { + "path": "scripts/probe-dual-lane.py", + "sha256": "03850a3923c3006cc7a45ecabfcda011abd7a7bc9048e3f6d0f6fb304e889fa3" + }, + { + "path": "scripts/probe-events.py", + "sha256": "7883b6e511390ac7828140f27847ae3864cf024e28a4b2f3428d56fd432170c5" + }, + { + "path": "scripts/probe-loopback-bind.py", + "sha256": "863992c8000510c75d0bc4715edb5d6c568c0d0d697e884c22ec1c944c258dd7" + }, + { + "path": "scripts/probe-m10-canvas-live.py", + "sha256": "fc74d61a948fbc50ee9f404b2d961294c1e1aa4c7f8abbcf69c21f248ff4b4da" + }, + { + "path": "scripts/probe-m6-live.py", + "sha256": "2d00c9e2093d098121f154bf8d52f6772337c8375485dfb4e11dcefd3304a497" + }, + { + "path": "scripts/probe-manifest-adapters-scales.py", + "sha256": "42bf2dadcc9f526dca404e207be8582f5a916562ce67e38cd5e3085515602dc7" + }, + { + "path": "scripts/probe-manifest-inventories.py", + "sha256": "e1d469816c5e1cffb4bf85f48161babb12e9d538de5f9c74b6ecfd39287339d0" + }, + { + "path": "scripts/probe-monitoring-devices.py", + "sha256": "557586b8e4417f8181212d9e351e4a08c6f1f8d958e140b80829031cd4d8d483" + }, + { + "path": "scripts/probe-multi-stream.py", + "sha256": "90e0d1e6965c1e14a7966fb2de7d8ed5a2ed96ebc09bc8ecb0cf1bc6ac98b1f2" + }, + { + "path": "scripts/probe-nvenc-preset.py", + "sha256": "d2abc054f2f7d5452fcecdf8bafd8ccc448f97147f930b10e2ab1ea721a2242f" + }, + { + "path": "scripts/probe-nvenc-profile-frontier.py", + "sha256": "08721121540c8a370c2df4439d076e25df7fa882f5b0343f7c0d3a3f0846b8fb" + }, + { + "path": "scripts/probe-nvenc-quality.py", + "sha256": "56cfafefd8e602d874ff12d457a1a18efab98efe97cf4333dc38558ab4e67b75" + }, + { + "path": "scripts/probe-output-effect.py", + "sha256": "5812e46e62821c8b98dac7e4c05ce3e49894922beed79e30437a94d9b8dc958e" + }, + { + "path": "scripts/probe-preview-transition.py", + "sha256": "823696f91b73d95a4c205172c628ba8ee7e2d7ecfc202b9803769fd44888b1ab" + }, + { + "path": "scripts/probe-program-audio.py", + "sha256": "14f892b332a2d24a9cd8d1465235f33efc5b075090b4ee78975f85dac7a68b8c" + }, + { + "path": "scripts/probe-qsv-preset.py", + "sha256": "63158078c323231c5cbdceaf0f630fac59c1a3cc73b6a73e33a8c1e8ec8ebcd6" + }, + { + "path": "scripts/probe-record-m2.py", + "sha256": "87eb13525941b0f5e8671c0762eafbe265e353b5b55a65b33690feac82859f6f" + }, + { + "path": "scripts/probe-record-split.py", + "sha256": "80cfcfced6fe540171f4059d314d0518337272aaa5f1e4abfb0f3689a321d0a3" + }, + { + "path": "scripts/probe-record.py", + "sha256": "fc8e5451adc5f02f120e7ad2e4f02bdc7d61503a4c5bf8a3399e3bc3abca3589" + }, + { + "path": "scripts/probe-replay.py", + "sha256": "7173f2ac365fe9754944b890dd970dce33a274087627cdd968cbb7b8692ba011" + }, + { + "path": "scripts/probe-scene-list-truth.py", + "sha256": "352de0ea0f69ce65fbe9574c80458a2769a040a30780afc1dc89ac67f95393c9" + }, + { + "path": "scripts/probe-scene-name-drift.py", + "sha256": "c9260e8299be50608c8855b26fd5a1730f4077b4b708cba226918f5d858aa045" + }, + { + "path": "scripts/probe-source-kinds.py", + "sha256": "02bdd2af16607ca6e7e3af4e87178ad3402b63f7aa1b3740884b3e3479a822cb" + }, + { + "path": "scripts/probe-source-telemetry.py", + "sha256": "799011292a655e380aa702232a9dbe8b5d6fc62593d8327e240a62abe0f0ce14" + }, + { + "path": "scripts/probe-spike-gpu-coexist.py", + "sha256": "b0fc4f44240c03cf9486f64d364088f08cb7341c8140ae09d7be1776eec751c3" + }, + { + "path": "scripts/probe-stinger-smoke.py", + "sha256": "11b530b34317d528acadb8d9cb707dd8e47213cded5fc2ed3642b1c0bb7dc026" + }, + { + "path": "scripts/probe-stream-egress-guard.py", + "sha256": "82bdfcdbe391620f5074b10dc80cccd4e95f39b8f5e144a54aa8ce54184c59e2" + }, + { + "path": "scripts/probe-take-latency.py", + "sha256": "66301bfa84e5a3c5c07d957cd267a3ae13593acba06b78b47612939965fd6bab" + }, + { + "path": "scripts/probe-transition-boundary.py", + "sha256": "ae63c09ea7363e1bdb761f8baa2181b2834a1858b7655f574d23e9e27660dd56" + }, + { + "path": "scripts/probe-transition-raw-boundary.py", + "sha256": "2b4b10b697a103f63c186ce5c8815b83634e89d99e08b6d432e0f62f8eaf8bb0" + }, + { + "path": "scripts/probe-twitch-live.py", + "sha256": "185e16ab1392f88063f3a8be5c1121f296e58685bd1cdb50f282a9f235e2aeb3" + }, + { + "path": "scripts/probe-twitch-rtmps.py", + "sha256": "573daf7e7b8b600f7dad9eb6d33a824d1b4229410533f59d00df84aa1e694d8a" + }, + { + "path": "scripts/probe-twitch-scene-switch.py", + "sha256": "0445b36b210bdc5f41430e99dc2ec5431f3705872e0540ab348d1038b0415020" + }, + { + "path": "scripts/probe-vcam-scene-mode.py", + "sha256": "634899c513921a9b273f52ac92025f754eb30f149186e6f32080c750fe63f1c6" + }, + { + "path": "scripts/probe-webpage-control-level.py", + "sha256": "b2609192a332b8cffa2f8b6efbe643d0146f96fab161af31a846fe8d4a3adf8b" + }, + { + "path": "scripts/probe-websocket.py", + "sha256": "f2b50510df33cc966c22ae383731be1b64ff674b6c834ab2c9cac493a1bec34b" + }, + { + "path": "scripts/publish-existing-release.py", + "sha256": "76dd6595e70b7720f3511819c9bbbd4e377cca5650afa2c6b0215a56091abe05" + }, + { + "path": "scripts/redact-websocket-config.py", + "sha256": "e54690a816f0505d21cb22ae6db24c2d46574548bdcc49f672219d93751d419b" + }, + { + "path": "scripts/report-253-study.py", + "sha256": "825caada45cb8257891e545658e667eb5b75967e5e35a61d84dbb66215b0710f" + }, + { + "path": "scripts/test-cef-provisioning.py", + "sha256": "c0ae8fb6356c811f7b7f14e6784f05b694e2b9df955e5d06e8a40ce453f0305f" + }, + { + "path": "scripts/test_ci_native_cache.py", + "sha256": "6d9c6a992d0a246412d9ca2df2b655eaf14344d3cbb9d3e7d86733bad60ba9bd" + }, + { + "path": "scripts/test_live_workflow.py", + "sha256": "73cc894f3db57bee3990f24dc092aa5565ea64890366f082100dbe487c611a69" + }, + { + "path": "scripts/test_m10_setup.py", + "sha256": "32cd867ca6096863c0425b3ed73119e8684acfade6413f8914ccae8bea13fe4f" + }, + { + "path": "scripts/test_prepare_pages_proof.py", + "sha256": "159753472cac9e2d57e738f0e6130f57790e71e21e05139435680d736038fed1" + }, + { + "path": "scripts/test_probe_m10_real_orion.py", + "sha256": "73091809f7e114cc9838aafe1228f082655d33e09508d0387a1174e01cda2761" + }, + { + "path": "scripts/test_probe_twitch_live.py", + "sha256": "ed0ebb6c2d5f308e151edfdcbd303fa77aa67008b39b5e5cc8be6f355bf11ebf" + }, + { + "path": "scripts/test_publish_existing_release.py", + "sha256": "a1ce560a98e7359e461b79be0a9c56106c7d012dd44e9986284fd87518a7c0bb" + }, + { + "path": "scripts/trace-integrity.py", + "sha256": "a7e2ed2d4c1b5ea2ffb60532cb434a76e3766c383c0a7ba2a52265bddd5e45bd" + }, + { + "path": "tests/build-win/test_build_win_fastpath.py", + "sha256": "e81c9263f3070e36bc26959d7d19fb22b33025153eed376898fbf2e11502877f" + }, + { + "path": "tests/cmake_external/test_external_upstream_build_dir.py", + "sha256": "769768d4b21fa896780d4d763446602c00993af1fae9821026b962fa0ec7a4ae" + }, + { + "path": "tests/d3d11-return-transport/test_d3d11_recovery_contract.py", + "sha256": "284198ce4c565ba8297484f1e58b4737a0d75a838c1842a73d432334d69ff797" + }, + { + "path": "tests/d3d11-return-transport/test_d3d11_return_transport.py", + "sha256": "865cc4105a126e5503ab6b5206e2bb110a27874a1ec9f6a0e704e3e561f12a17" + }, + { + "path": "tests/d3d11-return-transport/test_f02_readonly_abi_contract.py", + "sha256": "de59f32776fe18cd6ace56b7db2f95ae32ae81a9cd0ed8a253d59fc92e973d6e" + }, + { + "path": "tests/d3d11-return-transport/test_private_helper_auth_contract.py", + "sha256": "b8657a21d21ae8e70e2bbe0fa99b9fb2c642edfc55da096f047d6768a3371a55" + }, + { + "path": "tests/dir-hardening-probe/dir-hardening-probe-test.cpp", + "sha256": "87477fc9026584f507ea5a13607b94eb2a74c8a700d7f082d9471b634f21343d" + }, + { + "path": "tests/directshow-namespace-probe/directshow-namespace-probe-test.cpp", + "sha256": "7c7899bb73e81e3b040c250fa42a52e90036d8793619de82ef37e9a216292433" + }, + { + "path": "tests/dual-lane-config/dual-lane-config-probe.cpp", + "sha256": "3aacbe146761f007e1026250e027fc8bc5cf5c3ecba2c8b233191b77998769b5" + }, + { + "path": "tests/dual-lane-hot-swap/test_hot_lane_activation_contract.py", + "sha256": "bbaef3836fad18be446e5c38202c1697ec0b320dc67cfc46f9a11739be958fc2" + }, + { + "path": "tests/lease-watcher/registration-pipe-probe.cpp", + "sha256": "f9e51733c0022a5beaa716717de2d4773942a17455ac4748f228bbf5ff7844c3" + }, + { + "path": "tests/lease-watcher/test_consumer_registration_contract.py", + "sha256": "1bf47bd015b7e57e34a60f68bbc6529345ec39b497da6665ad57c60b57da0a4b" + }, + { + "path": "tests/lease-watcher/test_lease_watcher_contract.py", + "sha256": "b80bbc5bc79fca71c1fa2582414df1d55b3cdfefd4c0fb77e8a31594ed8273bf" + }, + { + "path": "tests/log-handler-probe/log-handler-probe-test.cpp", + "sha256": "a46524d0f52510467114a9361864c8fcafd45ba8b85344f15ac111a58c471999" + }, + { + "path": "tests/output-attempt-settled-probe/output-attempt-settled-probe-test.cpp", + "sha256": "d89151a53964a1a11c6c84272b54802a765a62b7a75b93b99e78f7bba580fd63" + }, + { + "path": "tests/output-classify-probe/output-classify-probe-test.cpp", + "sha256": "499a8eeb883183c18016a7ed0a1d556195e822a71a3c1abdd79669ef9b529348" + }, + { + "path": "tests/probe-audio-multitrack/test_probe_audio_multitrack_contract.py", + "sha256": "ece6b02b0e04adcb28e93eb5acab876e739288e2e23b3bf4b9cc5a4f048cd1ce" + }, + { + "path": "tests/probe-dual-lane/test_marker_metadata.py", + "sha256": "d44b93ecb2097db17d6ba6062686df1ff4cbdf9bcd4d2efb1ce791f952184862" + }, + { + "path": "tests/probe-dual-lane/test_native_packet_content.py", + "sha256": "d20a3efc1d87ebee312bb84928eaadce4682636154e2837979790ee1c84b3a64" + }, + { + "path": "tests/probe-dual-lane/test_native_receiver.py", + "sha256": "c7a0bae260854707db59d3bf25cc1c3d9697f1e6db9ff67f9609be8dd3a9927e" + }, + { + "path": "tests/probe-dual-lane/test_probe_dual_lane.py", + "sha256": "24fdc53e8754a1e494ddfeecdef6e205facb72f447897edbe0401f59bc9dd652" + }, + { + "path": "tests/probe-nvenc-async-output/test_async_output.py", + "sha256": "29cd13471146348390792fab1f0af327265f437ce177e22a53dd5aa4619da335" + }, + { + "path": "tests/probe-nvenc-quality/test_probe_nvenc_quality.py", + "sha256": "47f3172836ed035c692ed6aaa3776ea27c27d088f20e5db71987ebdccdd03790" + }, + { + "path": "tests/probe-nvenc-ready-drain/test_ready_drain.py", + "sha256": "e839adfa3424638ea7972f4e9027e3c8d66b9db58b8be3349397078270457d17" + }, + { + "path": "tests/probe-output-effect/test_probe_output_effect_contract.py", + "sha256": "9fab8a2a455e5084df6712e07833c95f02ac78be7c174a2a6e69a61e2c4eb843" + }, + { + "path": "tests/probe-scene-name-drift/test_probe_scene_name_drift_contract.py", + "sha256": "3087ed8adfa049694452e9459922e2e9f2754a0d269178502a1669f67d8819e5" + }, + { + "path": "tests/probe-take-latency/test_probe_take_latency.py", + "sha256": "0202640c169b206a00607172eaab7d3a76b7cf6b85f3b60ebad3e15ada07fdc5" + }, + { + "path": "tests/probe-take-latency/test_record_stop_contract.py", + "sha256": "5aff8468feff4575617df6fbb5a0782dbc4ae537c0d7eb062b8645aeb78e0e07" + }, + { + "path": "tests/pulsar-headless-shutdown/audio-callback-gate-test.cpp", + "sha256": "32cd2cbaf33a8ea528bec33a25b08d4b50ef767d90a39849f0993f2940e7d2d3" + }, + { + "path": "tests/pulsar-headless-shutdown/browser-source-task-state-test.cpp", + "sha256": "314e37c1ed60d663b582592b353c7d8840f4dfe8cbb4ce50c37a1f45a88a528b" + }, + { + "path": "tests/pulsar-headless-shutdown/test_graceful_shutdown.py", + "sha256": "7a7a8b6739bda299c6363a415ca5fb873235675b6298169825ac0174c1a2440d" + }, + { + "path": "tests/pulsar-headless-shutdown/test_websocket_quiesce.py", + "sha256": "08ddd6e3ccb2b73f33bd631ac837fb747cc1255b7c6c06c4cbedae406ce24847" + }, + { + "path": "tests/pulsar-media-transition/config-probe.cpp", + "sha256": "7fb5aaae32bccd62bd615b9b54450cd2fed99190cddf3e43c507b3af8a19b817" + }, + { + "path": "tests/pulsar-transition-controller/transition-controller-probe.cpp", + "sha256": "a96047557d5a1bd8f313e5d8f425947b4f61b1f7829c14307258ee15df09f666" + }, + { + "path": "tests/return-queue-safety/test_content_identity.py", + "sha256": "4cfcb904635f6af2002a74cdd8d698e3f06265733ea2008d57fd41f74296f48b" + }, + { + "path": "tests/return-queue-safety/test_current_readback_audio_clock.py", + "sha256": "ccdc9a5487191bc37a3957f35ec0d60ce6d150632bb9b1e23c9604219a3186ab" + }, + { + "path": "tests/return-queue-safety/test_pipeline_stats_atomic.py", + "sha256": "31c220fee3a9d258735e4bf96a417edac859b10717692bb6d53012adae760a01" + }, + { + "path": "tests/return-queue-safety/test_return_queue_safety.py", + "sha256": "0164aae165fc8f7cfb4c85d1c1e02bbdd5d84f4fe5dd5345c025e500fbc59bad" + }, + { + "path": "tests/return-queue-safety/test_tight_nv12_copy.py", + "sha256": "7b9cf01d920c8de00b2a6231ca4a15b1e4f1a56f70202e06ecd18f641057dbe1" + }, + { + "path": "tests/rtwq-lifecycle/probe-wasapi.py", + "sha256": "bc5205c7c7f4f6df40595a91c70fb98b298b596488e82472ae1d4507f0b25b24" + }, + { + "path": "tests/rtwq-lifecycle/rtwq-lifecycle-test.cpp", + "sha256": "b479b5f2047215aed3b6fe616d52c7b7c1d3ca705cc12200f188aaeba8379c1c" + }, + { + "path": "tests/runtime-isolation-probe/runtime-isolation-probe-test.cpp", + "sha256": "6b2a18a3489d3cb75d9ee67cbf8972a17e8f18c9e4ef922dfe64a4ad1f416c91" + }, + { + "path": "tests/telemetry-signals/test_telemetry_signals.py", + "sha256": "2a2983fbeb8555a54516b7402533cbde760a0e0ae55dc6314195e89c7753556d" + }, + { + "path": "tests/trace-integrity/test_trace_integrity.py", + "sha256": "eb2b6af57c84910bb98878d9754abffb4fdac0a1d1f07a20e37f99896809400c" + }, + { + "path": "tests/ux-251/consumer.mjs", + "sha256": "201bb79bbb669e230df888a32a74ff29a59b7fc5212b8a87bbc396e236afa8c2" + }, + { + "path": "tests/ux-251/consumer.test.mjs", + "sha256": "c25ab38df50f2b59f8d4e51c93f55a8a7ff11602d14d84df2c92958b655bbf56" + }, + { + "path": "tests/ws-debug-probe/ws-debug-probe-test.cpp", + "sha256": "904637381550a0fc3c7370e85790ad7b15e3d6208972d0cb69795747dcba4fe7" + } + ] +} \ No newline at end of file diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T101625Z-configurable-transition-regression.json b/evidence/local-20261009-capability-audit/eleven/20261009T101625Z-configurable-transition-regression.json new file mode 100644 index 00000000..47bda6b9 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T101625Z-configurable-transition-regression.json @@ -0,0 +1,730 @@ +{ + "passed": true, + "default": { + "busy": false, + "config": {}, + "configured": false, + "last_switch": {}, + "media_duration_ms": 0, + "operational": true, + "preview_overlay": {}, + "readiness_error": "NOT_CONFIGURED", + "ready": false, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "transition_duration_ms": 0 + }, + "ready": { + "busy": false, + "config": { + "cut_point_ms": 700, + "muted": false, + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\fix-configurable-probe\\fixture.webm", + "volume": 0.5 + }, + "configured": true, + "last_switch": {}, + "media_duration_ms": 1508, + "operational": true, + "preview_overlay": {}, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "transition_duration_ms": 1758 + }, + "same_program": { + "result": { + "command_id": "same-program", + "frame_id": 86, + "lane_id": "A", + "pts_ns": 7578990292805, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "green", + "status": "completed" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\fix-configurable-probe\\pulsar-20261009-113115.mkv", + "frames": 75, + "audio_rms": 0.04697546735405922, + "centers": [ + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 217.0, + 38.0, + 38.0 + ], + [ + 212.0, + 40.0, + 35.0 + ], + [ + 203.0, + 47.0, + 35.0 + ], + [ + 197.0, + 60.0, + 33.0 + ], + [ + 193.0, + 74.0, + 32.0 + ], + [ + 193.0, + 91.0, + 31.0 + ], + [ + 195.0, + 109.0, + 28.0 + ], + [ + 201.0, + 127.0, + 28.0 + ], + [ + 212.0, + 146.0, + 27.0 + ], + [ + 226.0, + 165.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 233.0, + 174.0, + 26.0 + ], + [ + 223.0, + 173.0, + 27.0 + ], + [ + 196.0, + 169.0, + 27.0 + ], + [ + 171.0, + 169.0, + 28.0 + ], + [ + 145.0, + 173.0, + 30.0 + ], + [ + 119.0, + 179.0, + 31.0 + ], + [ + 96.0, + 186.0, + 32.0 + ], + [ + 74.0, + 194.0, + 33.0 + ], + [ + 53.0, + 203.0, + 35.0 + ], + [ + 41.0, + 212.0, + 37.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ], + [ + 38.0, + 218.0, + 38.0 + ] + ] + }, + "same_preview": { + "result": { + "command_id": "same-preview", + "frame_id": 162, + "lane_id": "B", + "pts_ns": 7581523626113, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "red", + "status": "completed" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\fix-configurable-probe\\pulsar-20261009-113117.mkv", + "audio_rms": 0.0 + }, + "take": { + "state": { + "contract": "pulsar.scene-switch.v1", + "frozen": false, + "idempotency_cache_capacity": 4096, + "idempotency_cache_entries": 2, + "operational": true, + "revisions": { + "preview": 2, + "program": 2, + "role_map": 1 + }, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "schema_version": 1, + "server_seq": 4, + "state": "ready" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\fix-configurable-probe\\pulsar-20261009-113120.mkv", + "audio_rms": 0.04694736748933792, + "committed_event": { + "command_id": "test-take-command", + "contract": "pulsar.scene-switch.v1", + "event_type": "TakeCommitted", + "frame_id": 247, + "intent_id": "test-take", + "message_type": "event", + "observed_at_monotonic_ns": 7584358183700, + "payload_sha256": "e08ffe4965fffdce09fa28580b8d840c6cab3340f53421845b93fd2938ae7381", + "preview_lane_id": "A", + "previous_revisions": { + "preview": 2, + "program": 1, + "role_map": 0 + }, + "previous_role_map": { + "on_air": "A", + "preview": "B" + }, + "program_lane_id": "B", + "pts_ns": 7584356959418, + "revisions": { + "preview": 2, + "program": 2, + "role_map": 1 + }, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "schema_version": 1, + "server_seq": 4, + "source_lane_id": "A", + "state": "ready", + "take_command_id": "test-take-command", + "target_lane_id": "B", + "target_scene_id": "red" + } + }, + "abort": { + "command_id": "abort-me", + "frame_id": 280, + "lane_id": "B", + "pts_ns": 7585456959407, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "blue", + "status": "aborted" + }, + "replacement_muted": { + "state": { + "busy": false, + "config": { + "cut_point_ms": 900, + "muted": true, + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\fix-configurable-probe\\second-\u00e9ffect.webm", + "volume": 0.20000000298023224 + }, + "configured": true, + "last_switch": { + "command_id": "after-abort", + "frame_id": 345, + "lane_id": "B", + "pts_ns": 7587623626052, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "blue", + "status": "completed" + }, + "media_duration_ms": 1508, + "operational": true, + "preview_overlay": {}, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "transition_duration_ms": 1758 + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\fix-configurable-probe\\pulsar-20261009-113123.mkv", + "audio_rms": 0.0 + }, + "media_gain": { + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\fix-configurable-probe\\pulsar-20261009-113126.mkv", + "audio_rms": 0.018809134140610695, + "ratio_to_half_gain": 0.40040334242646697 + }, + "clear_cut": { + "result": { + "command_id": "clear-cut", + "frame_id": 469, + "lane_id": "B", + "pts_ns": 7591756959344, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "blue", + "status": "completed" + }, + "recording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\fix-configurable-probe\\pulsar-20261009-113129.mkv", + "audio_rms": 0.0 + }, + "mp4": { + "ready": { + "busy": false, + "config": { + "cut_point_ms": 700, + "muted": false, + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\fix-configurable-probe\\fixture.mp4", + "volume": 0.5 + }, + "configured": true, + "last_switch": { + "command_id": "noop", + "lane_id": "B", + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "blue", + "status": "noop" + }, + "media_duration_ms": 1500, + "operational": true, + "preview_overlay": {}, + "readiness_error": "", + "ready": true, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "transition_duration_ms": 1750 + }, + "result": { + "command_id": "mp4-program", + "frame_id": 550, + "lane_id": "B", + "pts_ns": 7594456959317, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "red", + "status": "completed" + } + }, + "terminal_events": [ + { + "command_id": "same-program", + "frame_id": 86, + "lane_id": "A", + "pts_ns": 7578990292805, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "green", + "status": "completed" + }, + { + "command_id": "same-preview", + "frame_id": 162, + "lane_id": "B", + "pts_ns": 7581523626113, + "role_map": { + "on_air": "A", + "preview": "B" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "red", + "status": "completed" + }, + { + "command_id": "abort-me", + "frame_id": 280, + "lane_id": "B", + "pts_ns": 7585456959407, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "blue", + "status": "aborted" + }, + { + "command_id": "after-abort", + "frame_id": 345, + "lane_id": "B", + "pts_ns": 7587623626052, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "blue", + "status": "completed" + }, + { + "command_id": "lower-gain", + "frame_id": 435, + "lane_id": "B", + "pts_ns": 7590623626022, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "red", + "status": "completed" + }, + { + "command_id": "clear-cut", + "frame_id": 469, + "lane_id": "B", + "pts_ns": 7591756959344, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "blue", + "status": "completed" + }, + { + "command_id": "mp4-program", + "frame_id": 550, + "lane_id": "B", + "pts_ns": 7594456959317, + "role_map": { + "on_air": "B", + "preview": "A" + }, + "runtime_instance_id": "runtime-ca535ef1799b4ab9", + "scene_name": "red", + "status": "completed" + } + ], + "shutdown": { + "exit_code": 0, + "forced": false + } +} \ No newline at end of file diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T101625Z-fix-report.json b/evidence/local-20261009-capability-audit/eleven/20261009T101625Z-fix-report.json new file mode 100644 index 00000000..9ad67a5f --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T101625Z-fix-report.json @@ -0,0 +1,28 @@ +{ + "kind": "AGENT_REPORT", + "owner": "Eleven", + "scope": "User-authorized capability audit corrections without ADR or agents", + "changes": [ + "Reject screenshots of zero-dimensional sources before any GPU allocation", + "Reject impossible dimensions and failed image/graphics allocations without dereferencing invalid resources", + "Reusable isolated synthetic source screenshot/filter lifecycle regression" + ], + "validation": { + "websocketDllSha256": "e93ad8bd2ae86e9cdb2f6ba2b3c70a92abe3969d46db422a3347026ee3341447", + "reproducedBefore": "D3D11 80070057", + "fixedProbe": { + "passed": true, + "websocket_sha256": "e93ad8bd2ae86e9cdb2f6ba2b3c70a92abe3969d46db422a3347026ee3341447", + "screenshot_dimension_modes": 4, + "modern_filter_lifecycle": true, + "staging_errors": 0, + "shutdown": { + "exit_code": 0, + "forced": false + } + }, + "activeDllBackup": "D:/Documents/Zab/Artifacts/backups/2026-10-09/pulsar-capability-fixes/obs-websocket.dll", + "previewAndConfigurableTransitionProbes": "passed" + }, + "handoff": "Source fixes are isolated on eleven/local-20261009-capability-audit. Shared Prism uses a current integration snapshot preserving concurrent work; that build is distinct from a clean Git checkout. No real Live or recording was started." +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T101625Z-screenshot-filter-regression.json b/evidence/local-20261009-capability-audit/eleven/20261009T101625Z-screenshot-filter-regression.json new file mode 100644 index 00000000..46ae5d99 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T101625Z-screenshot-filter-regression.json @@ -0,0 +1,11 @@ +{ + "passed": true, + "websocket_sha256": "e93ad8bd2ae86e9cdb2f6ba2b3c70a92abe3969d46db422a3347026ee3341447", + "screenshot_dimension_modes": 4, + "modern_filter_lifecycle": true, + "staging_errors": 0, + "shutdown": { + "exit_code": 0, + "forced": false + } +} \ No newline at end of file diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T102000Z-screenshot-before.json b/evidence/local-20261009-capability-audit/eleven/20261009T102000Z-screenshot-before.json new file mode 100644 index 00000000..7c5763a4 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T102000Z-screenshot-before.json @@ -0,0 +1,62 @@ +{ + "phase": "synthetic-local-only", + "started": 1791537451.9868557, + "checks": { + "all_real_audio_muted": [ + { + "name": "PulsarDesktopAudio", + "muted": true + } + ], + "synthetic_program_items_only": true, + "program": { + "currentProgramSceneName": "AuditSynthetic", + "currentProgramSceneUuid": "280a7749-7c42-413b-b784-43e2a036ff72", + "sceneName": "AuditSynthetic", + "sceneUuid": "280a7749-7c42-413b-b784-43e2a036ff72" + }, + "video": { + "baseHeight": 1080, + "baseWidth": 1920, + "fpsDenominator": 1, + "fpsNumerator": 60, + "outputHeight": 1080, + "outputWidth": 1920 + } + }, + "before": { + "activeFps": 60.0000024000001, + "availableDiskSpace": 123683.37890625, + "averageFrameRenderTime": 0.462672, + "cpuUsage": 1.6998665227718324, + "memoryUsage": 93.61328125, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 102, + "webSocketSessionIncomingMessages": 17, + "webSocketSessionOutgoingMessages": 31 + }, + "screenshot_status": { + "code": 702, + "comment": "Failed to render screenshot.", + "result": false + }, + "exit_code": 0, + "forced_kill": false, + "elapsed_s": 3.5053629875183105, + "staging_errors": [ + "2026-10-09T09:17:34Z ERROR 7aI_xIE8Adn9WwAL libobs | device_stagesurface_create (D3D11): Failed to create staging surface (80070057)" + ], + "encoder_markers": [ + "2026-10-09T09:17:33Z INFO 7aI_xIE8Adn9WwAL pulsar-frontend-stub | video encoder allocated: family=nvenc id=jim_nvenc, 6000 kbps (stable ProgramView binding follows)" + ], + "shutdown_markers": [ + "PULSAR_SHUTDOWN_CONTROL event=ready id=audit-synthetic-20261009 mechanism=inherited_event", + "PULSAR_SHUTDOWN_CONTROL event=signaled id=audit-synthetic-20261009 mechanism=inherited_event", + "2026-10-09T09:17:34Z INFO 7aI_xIE8Adn9WwAL libobs | PULSAR_CEF_SHUTDOWN event=pre_obs_shutdown_begin", + "2026-10-09T09:17:34Z INFO 7aI_xIE8Adn9WwAL libobs | PULSAR_CEF_SHUTDOWN event=pre_obs_shutdown_complete reason=cef_not_running", + "PULSAR_RUNTIME_INSTANCE runtime_dir_lease=released id=audit-synthetic-20261009 dir=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\runtimes\\audit-synthetic-20261009 authority=Local\\Pulsar.Authority.31eb85ecb5c9d1cf metadata=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\authorities\\session-1\\31eb85ecb5c9d1cf.lock", + "PULSAR_RUNTIME_INSTANCE lease=released id=audit-synthetic-20261009 dir=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\runtimes\\audit-synthetic-20261009 authority=Local\\Pulsar.Runtime.audit-synthetic-20261009 metadata=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\authorities\\session-1\\b8fa9ec736f9a3bb.lock" + ] +} \ No newline at end of file diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T102100Z-screenshot-after.json b/evidence/local-20261009-capability-audit/eleven/20261009T102100Z-screenshot-after.json new file mode 100644 index 00000000..1d379740 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T102100Z-screenshot-after.json @@ -0,0 +1,62 @@ +{ + "phase": "synthetic-local-only", + "started": 1791538656.0153248, + "checks": { + "all_real_audio_muted": [ + { + "name": "PulsarDesktopAudio", + "muted": true + } + ], + "synthetic_program_items_only": true, + "program": { + "currentProgramSceneName": "AuditSynthetic", + "currentProgramSceneUuid": "37901f41-391d-4fb9-ab8e-2921b17eb02e", + "sceneName": "AuditSynthetic", + "sceneUuid": "37901f41-391d-4fb9-ab8e-2921b17eb02e" + }, + "video": { + "baseHeight": 1080, + "baseWidth": 1920, + "fpsDenominator": 1, + "fpsNumerator": 60, + "outputHeight": 1080, + "outputWidth": 1920 + }, + "native_modern_filter_create_update_read_remove": true, + "valid_screenshots_four_dimension_modes": true + }, + "before": { + "activeFps": 60.0000024000001, + "availableDiskSpace": 123037.9609375, + "averageFrameRenderTime": 0.615759, + "cpuUsage": 1.7068603295582254, + "memoryUsage": 93.84375, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 95, + "webSocketSessionIncomingMessages": 17, + "webSocketSessionOutgoingMessages": 31 + }, + "screenshot_status": { + "code": 702, + "comment": "Failed to render screenshot.", + "result": false + }, + "exit_code": 0, + "forced_kill": false, + "elapsed_s": 3.216407299041748, + "staging_errors": [], + "encoder_markers": [ + "2026-10-09T09:37:37Z INFO cYfNcLYrUrXWb-oJ pulsar-frontend-stub | video encoder allocated: family=nvenc id=jim_nvenc, 6000 kbps (stable ProgramView binding follows)" + ], + "shutdown_markers": [ + "PULSAR_SHUTDOWN_CONTROL event=ready id=audit-synthetic-20261009 mechanism=inherited_event", + "PULSAR_SHUTDOWN_CONTROL event=signaled id=audit-synthetic-20261009 mechanism=inherited_event", + "2026-10-09T09:37:38Z INFO cYfNcLYrUrXWb-oJ libobs | PULSAR_CEF_SHUTDOWN event=pre_obs_shutdown_begin", + "2026-10-09T09:37:38Z INFO cYfNcLYrUrXWb-oJ libobs | PULSAR_CEF_SHUTDOWN event=pre_obs_shutdown_complete reason=cef_not_running", + "PULSAR_RUNTIME_INSTANCE runtime_dir_lease=released id=audit-synthetic-20261009 dir=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\runtimes\\audit-synthetic-20261009 authority=Local\\Pulsar.Authority.31eb85ecb5c9d1cf metadata=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\authorities\\session-1\\31eb85ecb5c9d1cf.lock", + "PULSAR_RUNTIME_INSTANCE lease=released id=audit-synthetic-20261009 dir=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\runtimes\\audit-synthetic-20261009 authority=Local\\Pulsar.Runtime.audit-synthetic-20261009 metadata=C:\\Users\\Mathias\\AppData\\Local\\Pulsar\\authorities\\session-1\\b8fa9ec736f9a3bb.lock" + ] +} \ No newline at end of file diff --git a/evidence/local-20261009-capability-audit/eleven/20261009T104000Z-preview-fix-probe.json b/evidence/local-20261009-capability-audit/eleven/20261009T104000Z-preview-fix-probe.json new file mode 100644 index 00000000..766d4e15 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261009T104000Z-preview-fix-probe.json @@ -0,0 +1,2396 @@ +{ + "passed": true, + "exe_sha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "runs": [ + { + "preparation_delay_ms": 800.0, + "capture_ms": 63.64120000034745, + "cut_window_wall_ms": 418.51990000031947, + "cut_window_media_ms": 400, + "samples": [ + { + "elapsed_ms": 1.1217000001124688, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "starting" + }, + { + "elapsed_ms": 46.70189999978902, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 92.96289999929286, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 140.9616000000824, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 188.61849999939295, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 235.3672999997798, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 282.41739999975835, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 328.719899999669, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 375.4598999994414, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 234, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 421.90320000008796, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 468.35190000001603, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 334, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 516.0357999993721, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 562.7426999999443, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 466, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 609.9882999997135, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 657.510199999706, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 566, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 704.390599999897, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 749.940499999866, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 634, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "closing" + }, + { + "elapsed_ms": 797.4993999996514, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 841.8950999994195, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 888.2275999994818, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 766, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 935.4669999993348, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 982.1545999993759, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1028.508200000033, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1074.5892999993885, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 934, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1121.3134999998147, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1000, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1167.5599000000147, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1034, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1215.0887999996485, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1133, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1261.2245000000257, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1134, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1307.563599999412, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1354.7116999998252, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1234, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1400.739099999555, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1448.379099999329, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1334, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1493.683400000009, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1540.5149999996866, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1586.764900000162, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1634.1944999994666, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "opening" + }, + { + "elapsed_ms": 1681.0491999995065, + "channel": "preview", + "command_id": "continuous-preview-0", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 97, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 47, + "playback_start_pts_ns": 7547216918918, + "pts_ns": 7548883585568, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 21, + "start_pts_ns": 7546350252260, + "status": "completed" + } + ] + }, + { + "preparation_delay_ms": 0, + "capture_ms": 106.66579999997339, + "cut_window_wall_ms": 376.26650000038353, + "cut_window_media_ms": 400, + "samples": [ + { + "elapsed_ms": 0.9520000003249152, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "starting" + }, + { + "elapsed_ms": 59.97610000031273, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 107.344799999737, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 155.07549999983894, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 200.58810000045924, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 247.87910000031843, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 294.17149999972025, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 341.41619999991235, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 233, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 389.6316000000297, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 266, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 435.52720000025147, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 483.56560000047466, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 530.5796000002374, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 400, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 577.2279000002527, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 466, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 624.593300000015, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 500, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 673.5218000003442, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 566, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 719.5960999997624, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 765.5338000004122, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 666, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "closing" + }, + { + "elapsed_ms": 813.3214999998017, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 859.8344000001816, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 734, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 907.4139000003925, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 953.7621999998009, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 833, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1000.8598000003985, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1047.8281000005154, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 933, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1094.239000000016, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1142.1691000005012, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1000, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1190.213699999731, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1235.455600000023, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1283.670599999823, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1134, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1330.5853000001662, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1377.1950999998808, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1266, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1423.7724000004164, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1469.7361999997156, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1334, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1517.1877000002496, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1563.4510999998383, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1466, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1610.7414999996763, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1658.9112999999998, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "opening" + }, + { + "elapsed_ms": 1706.0550000005605, + "channel": "preview", + "command_id": "continuous-preview-1", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 151, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 101, + "playback_start_pts_ns": 7549016918900, + "pts_ns": 7550683585550, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 98, + "start_pts_ns": 7548916918901, + "status": "completed" + } + ] + }, + { + "preparation_delay_ms": 350.0, + "capture_ms": 58.14040000041132, + "cut_window_wall_ms": 375.4018999998152, + "cut_window_media_ms": 367, + "samples": [ + { + "elapsed_ms": 0.552100000277278, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "starting" + }, + { + "elapsed_ms": 46.81590000018332, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_time_ms": 0, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 95.7048999998733, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 140.38979999986623, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 187.26040000001376, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 234.5461000004434, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 281.44890000021405, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 134, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 329.6239000001151, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 375.0205000005735, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 234, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 422.38350000025093, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 468.9481000004889, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 515.5998999998701, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 562.9415000003064, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 434, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 609.1974999999366, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 466, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 656.195800000205, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 533, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 702.6708000003055, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 749.4698000000426, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 797.5522000006094, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 666, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 845.0840000004973, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 890.4269000004206, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 734, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 936.913800000184, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 983.9970000002722, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 866, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1031.5977000000203, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1079.2553000001135, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1124.8424000004889, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1170.9881000006135, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1034, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1218.57910000017, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1264.5841000003202, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1134, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1311.538400000245, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1166, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1356.758699999773, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1404.047900000478, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1451.716600000509, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1499.8482999999396, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1366, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1547.0142000003761, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1595.1297000001432, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1639.9504999999408, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1686.7099999999482, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1735.674200000176, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 215, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "pts_ns": 7552816918862, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "completed" + } + ] + } + ], + "last_attempt": { + "preparation_delay_ms": 350.0, + "samples": [ + { + "elapsed_ms": 0.552100000277278, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "operational": true, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "starting" + }, + { + "elapsed_ms": 46.81590000018332, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_time_ms": 0, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 95.7048999998733, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 34, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 140.38979999986623, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 187.26040000001376, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 234.5461000004434, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 100, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 281.44890000021405, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 134, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 329.6239000001151, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 200, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 375.0205000005735, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 234, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 422.38350000025093, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 300, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 468.9481000004889, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 515.5998999998701, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 562.9415000003064, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 434, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 609.1974999999366, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 466, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 656.195800000205, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 533, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 702.6708000003055, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 749.4698000000426, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 600, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 797.5522000006094, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "media_paused": false, + "media_time_ms": 666, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "closing" + }, + { + "elapsed_ms": 845.0840000004973, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 700, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 890.4269000004206, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 734, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 936.913800000184, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 800, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 983.9970000002722, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 866, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1031.5977000000203, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 900, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1079.2553000001135, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1124.8424000004889, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 966, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1170.9881000006135, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1034, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1218.57910000017, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1100, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1264.5841000003202, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1134, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1311.538400000245, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1166, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1356.758699999773, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1200, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1404.047900000478, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1451.716600000509, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1300, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1499.8482999999396, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1366, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1547.0142000003761, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1400, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1595.1297000001432, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1639.9504999999408, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1686.7099999999482, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "opening" + }, + { + "elapsed_ms": 1735.674200000176, + "channel": "preview", + "command_id": "continuous-preview-2", + "cover_kind": "outgoing_frame", + "cut_time_ms": 700, + "frame_id": 215, + "media_paused": false, + "media_time_ms": 1500, + "operational": true, + "playback_start_frame_id": 165, + "playback_start_pts_ns": 7551150252212, + "pts_ns": 7552816918862, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 152, + "start_pts_ns": 7550716918883, + "status": "completed" + } + ] + }, + "aborts": [ + { + "phase": "preparing", + "result": { + "channel": "preview", + "command_id": "abort-preparing", + "cover_kind": "outgoing_frame", + "frame_id": 220, + "operational": true, + "pts_ns": 7552983585527, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 217, + "start_pts_ns": 7552883585528, + "status": "aborted" + } + }, + { + "phase": "playing", + "result": { + "channel": "preview", + "command_id": "abort-playing", + "cover_kind": "outgoing_frame", + "frame_id": 239, + "media_paused": false, + "media_time_ms": 366, + "operational": true, + "playback_start_frame_id": 225, + "playback_start_pts_ns": 7553150252192, + "pts_ns": 7553616918854, + "runtime_instance_id": "runtime-f6ee207a02f84aca", + "start_frame_id": 222, + "start_pts_ns": 7553050252193, + "status": "aborted" + } + } + ], + "shutdown": { + "exit_code": 0, + "forced": false + } +} diff --git a/plugins/pulsar-websocket/README.md b/plugins/pulsar-websocket/README.md index 0e854be1..b44fbc9e 100644 --- a/plugins/pulsar-websocket/README.md +++ b/plugins/pulsar-websocket/README.md @@ -103,3 +103,11 @@ permanent profiling as a compatibility fallback. `cpuTickMaxMs`, `cpuRenderFirstPassMaxMs` and `gpuRenderFirstPassMaxMs` report individual tick/first-render maxima. They are not combined per-frame maxima for the summed CPU/GPU average costs. + +Screenshot requests refuse inputs without video dimensions before allocating +D3D11 resources, including width-only and height-only requests. Computed dimensions +are bounded by D3D11 texture limits; image/render/staging allocation failures +return the existing processing failure without dereferencing null resources. +`scripts/probe-source-screenshot.py --exe --output ` +verifies unavailable/valid sources and modern filter round-trips with synthetic +inputs, no recording, and graceful shutdown. diff --git a/plugins/pulsar-websocket/src/requesthandler/RequestHandler_Sources.cpp b/plugins/pulsar-websocket/src/requesthandler/RequestHandler_Sources.cpp index 87cd2b95..4c4c744b 100644 --- a/plugins/pulsar-websocket/src/requesthandler/RequestHandler_Sources.cpp +++ b/plugins/pulsar-websocket/src/requesthandler/RequestHandler_Sources.cpp @@ -31,9 +31,14 @@ with this program. If not, see QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t requestedWidth = 0, uint32_t requestedHeight = 0) { + success = false; + // An input may exist before its video frame arrives, or be audio-only. + // Never allocate a D3D11 staging texture from unavailable dimensions. // Get info about the requested source const uint32_t sourceWidth = obs_source_get_width(source); const uint32_t sourceHeight = obs_source_get_height(source); + if (!sourceWidth || !sourceHeight) + return {}; const double sourceAspectRatio = ((double)sourceWidth / (double)sourceHeight); uint32_t imgWidth = sourceWidth; @@ -55,8 +60,13 @@ QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t reques imgWidth = ((double)imgHeight * sourceAspectRatio); } + if (!imgWidth || !imgHeight || imgWidth > 16384 || imgHeight > 16384) + return {}; + // Create final image texture QImage ret(imgWidth, imgHeight, QImage::Format::Format_RGBA8888); + if (ret.isNull()) + return {}; ret.fill(0); // Video image buffer @@ -69,7 +79,12 @@ QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t reques gs_texrender_t *texRender = gs_texrender_create(GS_RGBA, GS_ZS_NONE); gs_stagesurf_t *stageSurface = gs_stagesurface_create(imgWidth, imgHeight, GS_RGBA); - success = false; + if (!texRender || !stageSurface) { + gs_stagesurface_destroy(stageSurface); + gs_texrender_destroy(texRender); + obs_leave_graphics(); + return {}; + } gs_texrender_reset(texRender); if (gs_texrender_begin(texRender, imgWidth, imgHeight)) { vec4 background; diff --git a/scripts/probe-source-screenshot.py b/scripts/probe-source-screenshot.py new file mode 100644 index 00000000..ef8b0934 --- /dev/null +++ b/scripts/probe-source-screenshot.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Isolated screenshot/filter regression. Synthetic inputs only; no recording.""" +import argparse, asyncio, base64, hashlib, importlib.util, json, os, sys +from pathlib import Path +import websockets + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location("screenshot_transport", ROOT / "scripts/probe-configurable-transition.py") +transport = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = transport +spec.loader.exec_module(transport) + +async def exercise(proc, report): + proc.spawn() + await asyncio.to_thread(proc.wait_for, transport.process.READY_RE, 45) + await asyncio.to_thread(proc.wait_for_shutdown_control_ready, 10) + async with websockets.connect(f"ws://127.0.0.1:{proc.port}", max_size=32*1024*1024) as ws: + hello = json.loads(await ws.recv())["d"] + identify = {"rpcVersion": 1, "eventSubscriptions": 0x7ff} + if "authentication" in hello: + auth = hello["authentication"] + secret = base64.b64encode(hashlib.sha256((proc.password + auth["salt"]).encode()).digest()).decode() + identify["authentication"] = base64.b64encode(hashlib.sha256((secret + auth["challenge"]).encode()).digest()).decode() + await ws.send(json.dumps({"op":1,"d":identify})) + assert json.loads(await ws.recv())["op"] == 2 + wire = transport.Wire(ws) + for item in (await wire.call("GetInputList"))["inputs"]: + if item["inputKind"].startswith("wasapi"): + await wire.call("SetInputMute", {"inputName":item["inputName"], "inputMuted":True}) + await wire.call("CreateScene", {"sceneName":"ScreenshotRegression"}) + await wire.call("CreateInput", {"sceneName":"ScreenshotRegression", "inputName":"NoVideo", "inputKind":"ffmpeg_source", "inputSettings":{}, "sceneItemEnabled":False}) + await wire.call("CreateInput", {"sceneName":"ScreenshotRegression", "inputName":"SyntheticColor", "inputKind":"color_source_v3", "inputSettings":{"color":0xffff00ff,"width":320,"height":180}, "sceneItemEnabled":True}) + modes = [{}, {"imageWidth":240}, {"imageHeight":135}, {"imageWidth":240,"imageHeight":135}] + for dimensions in modes: + status = await wire.call("GetSourceScreenshot", {"sourceName":"NoVideo", "imageFormat":"png", **dimensions}, error=True) + assert status["code"] == 702, status + valid = await wire.call("GetSourceScreenshot", {"sourceName":"SyntheticColor", "imageFormat":"png", **dimensions}) + assert valid["imageData"].startswith("data:image/png;base64,") + report["screenshot_dimension_modes"] = len(modes) + for logical in ["color_filter", "chroma_key_filter"]: + kind = logical + "_v2" + defaults = await wire.call("GetSourceFilterDefaultSettings", {"filterKind":kind}) + assert defaults["defaultFilterSettings"]["opacity"] == 1 + name = "prism/" + logical + await wire.call("CreateSourceFilter", {"sourceName":"SyntheticColor", "filterName":name, "filterKind":kind, "filterSettings":{"opacity":0.5}}) + await wire.call("SetSourceFilterSettings", {"sourceName":"SyntheticColor", "filterName":name, "filterSettings":{"opacity":0.75}, "overlay":True}) + actual = await wire.call("GetSourceFilter", {"sourceName":"SyntheticColor", "filterName":name}) + assert actual["filterKind"] == kind and actual["filterSettings"]["opacity"] == 0.75 + await wire.call("RemoveSourceFilter", {"sourceName":"SyntheticColor", "filterName":name}) + report["modern_filter_lifecycle"] = True + await asyncio.sleep(.2) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--exe", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + args.output.mkdir(parents=True, exist_ok=True) + os.environ["PULSAR_DIRECTSHOW_LEGACY_ALIAS"] = "0" + os.environ["PULSAR_LEGACY_ALIAS"] = "disabled" + proc = transport.process.PulsarProcess(args.exe.resolve(), "nvenc", args.output.resolve()) + report = {"passed":False, "websocket_sha256":hashlib.sha256((args.exe.resolve().parents[2]/"obs-plugins/64bit/obs-websocket.dll").read_bytes()).hexdigest()} + try: + asyncio.run(exercise(proc, report)) + except Exception as error: + report["error_type"] = type(error).__name__ + raise + finally: + proc.shutdown() + lines = proc.snapshot() + report["staging_errors"] = sum("Failed to create staging surface" in line for line in lines) + report["shutdown"] = {"exit_code":proc.proc.poll(), "forced":proc.forced_kill_used} + report["passed"] = "error_type" not in report and report["staging_errors"] == 0 and report["shutdown"] == {"exit_code":0,"forced":False} + (args.output/"result.json").write_text(json.dumps(report,indent=2),encoding="utf8") + print(json.dumps(report)) + sys.exit(0 if report["passed"] else 1) From c05dca2627afbfa42b3cc8bb5e830432147c390f Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Sat, 10 Oct 2026 09:52:57 +0200 Subject: [PATCH 08/17] test: record native filter audit evidence --- .../eleven/20261010T074000Z-checkpoint.json | 13 ++ .../20261010T074000Z-evidence-hashes.json | 22 +++ .../eleven/20261010T074000Z-filter-probe.txt | 59 +++++++ .../20261010T074000Z-native-filters.json | 153 ++++++++++++++++++ .../eleven/20261010T074000Z-provenance.json | 14 ++ ...0261010T074000Z-screenshot-regression.json | 14 ++ 6 files changed, 275 insertions(+) create mode 100644 evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-checkpoint.json create mode 100644 evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-evidence-hashes.json create mode 100644 evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-filter-probe.txt create mode 100644 evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-native-filters.json create mode 100644 evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-provenance.json create mode 100644 evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-screenshot-regression.json diff --git a/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-checkpoint.json b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-checkpoint.json new file mode 100644 index 00000000..a277b3e9 --- /dev/null +++ b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-checkpoint.json @@ -0,0 +1,13 @@ +{ + "type": "AGENT_CHECKPOINT", + "mandate": "NVIDIA Broadcast vers Prism, 27 capacites", + "time": "2026-10-10T07:48:08.726012+00:00", + "worktree": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit", + "branch": "eleven/local-20261009-capability-audit", + "baseRevision": "e8d0ce456e1c9ee763f6f3d8049b4162ebee7d59", + "changedFiles": "Only evidence/local-20261010-nvidia-broadcast-audit/eleven; no product source changes", + "validation": "Prism:319/339 targeted test union,20 retained failures,27-row report browser QA; Pulsar:7 classic native filter cycles plus4 screenshot modes andclean shutdown exit0", + "runtimeOwnership": "Scene-sync retains common dev; no restart/install by this audit", + "remaining": "AI NVIDIA unavailable (SDK absent), native source telemetry incompatible with loaded libobs, missing/partial capability rows,20test failures; noProgram/live/recording or physical disconnect test", + "retention": "Audit worktree/branch still attached to unmerged capability-audit PR; foreign and dirty worktrees preserved" +} diff --git a/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-evidence-hashes.json b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-evidence-hashes.json new file mode 100644 index 00000000..315cd7a8 --- /dev/null +++ b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-evidence-hashes.json @@ -0,0 +1,22 @@ +[ + { + "path": "20261010T074000Z-checkpoint.json", + "sha256": "c4811a20133e008a117d76344e6f6c24bfe2cbd8be5020b696402aecd69660e0" + }, + { + "path": "20261010T074000Z-filter-probe.txt", + "sha256": "c8a5b8bf7379ffa22f271e0b0ea8b5286f4d52058a6fb26666f6cd4865b131a9" + }, + { + "path": "20261010T074000Z-native-filters.json", + "sha256": "70745d125571c824e8d833ef20153c2c9fd1255bdcdf2374ba46b0c583cf46d0" + }, + { + "path": "20261010T074000Z-provenance.json", + "sha256": "d4bbbfc5d87a7693528ad7dd2b75d2079cfae75caf08d26f2398858c15442a02" + }, + { + "path": "20261010T074000Z-screenshot-regression.json", + "sha256": "5fddbcb4cd2f188c4b555ee12a52a2583bc148ef65eef2019d0ae37953d95220" + } +] diff --git a/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-filter-probe.txt b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-filter-probe.txt new file mode 100644 index 00000000..f756f07e --- /dev/null +++ b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-filter-probe.txt @@ -0,0 +1,59 @@ +import asyncio,base64,hashlib,importlib.util,io,json,os,sys,wave,math,struct,time +from pathlib import Path +from PIL import Image +import websockets +root=Path('D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261009-capability-audit') +spec=importlib.util.spec_from_file_location('stack_transport',root/'scripts/probe-configurable-transition.py') +t=importlib.util.module_from_spec(spec);sys.modules[spec.name]=t;spec.loader.exec_module(t) +work=root/'.cache/nvidia-filter-stack';work.mkdir(parents=True,exist_ok=True) +proof=root/'evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T074000Z-native-filters.json' +tone=work/'tone.wav' +with wave.open(str(tone),'wb') as w: + w.setparams((1,2,48000,0,'NONE','not compressed')) + w.writeframes(b''.join(struct.pack(' Date: Sat, 10 Oct 2026 10:48:34 +0200 Subject: [PATCH 09/17] feat: add native alpha-preserving camera vignette --- CMakeLists.txt | 5 + README.md | 6 + docs/ARCHITECTURE.md | 1 + .../20261010T082800Z-vignette-native.json | 793 ++++++++++++++++++ .../eleven/20261010T084713Z-feature-report.md | 13 + .../20261010T090000Z-vignette-native.json | 43 + .../20261010T090500Z-vignette-native.json | 69 ++ plugins/pulsar-camera-effects/CMakeLists.txt | 18 + plugins/pulsar-camera-effects/README.md | 20 + .../pulsar-camera-effects/camera-effects.cpp | 114 +++ scripts/probe-camera-effects.py | 139 +++ 11 files changed, 1221 insertions(+) create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T082800Z-vignette-native.json create mode 100644 evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T084713Z-feature-report.md create mode 100644 evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T090000Z-vignette-native.json create mode 100644 evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T090500Z-vignette-native.json create mode 100644 plugins/pulsar-camera-effects/CMakeLists.txt create mode 100644 plugins/pulsar-camera-effects/README.md create mode 100644 plugins/pulsar-camera-effects/camera-effects.cpp create mode 100644 scripts/probe-camera-effects.py diff --git a/CMakeLists.txt b/CMakeLists.txt index ab739926..4ead2c3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ option(PULSAR_BUILD_WEBSOCKET "Build the pulsar-websocket plugin" option(PULSAR_BUILD_MULTISTREAM "Build the pulsar-multi-stream plugin" ON) option(PULSAR_BUILD_SCENE_SOURCE "Build the pulsar-scene-source plugin" ON) option(PULSAR_BUILD_BROWSER "Build the pulsar-browser plugin (forked CEF)" ON) +option(PULSAR_BUILD_CAMERA_EFFECTS "Build native GPU camera effects" ON) # Header-only, no build of its own, and needed by pulsar-multi-stream and # by tests/nv-probe -- so it is added before both. (upstream's nv-filters @@ -82,6 +83,10 @@ if(PULSAR_BUILD_SCENE_SOURCE) add_subdirectory(plugins/pulsar-scene-source) endif() +if(PULSAR_BUILD_CAMERA_EFFECTS) + add_subdirectory(plugins/pulsar-camera-effects) +endif() + if(PULSAR_BUILD_BROWSER) add_subdirectory(plugins/pulsar-browser) endif() diff --git a/README.md b/README.md index dabf8616..ddca2231 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,12 @@ file path. See [destinations](plugins/pulsar-multi-stream/README.md) and the ## Capture, composition and browser rendering +The native [camera-effects module](plugins/pulsar-camera-effects/README.md) adds +`pulsar_vignette_filter`. It darkens source edges on the GPU while preserving +alpha, with independent enable/bypass and bounded intensity, radius and softness. +Hosts expose it only when the runtime filter inventory registers it. It does +not require NVIDIA AI SDKs or create another capture producer. + Pulsar retains OBS's source and scene model. The engine loads the source types provided by the selected bundle and the host's available hardware; query `GetInputKindList` and the capability manifest instead of assuming that a DLL diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 57cce677..a309a7cf 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -62,6 +62,7 @@ consumer. The runtime remains GPL-2.0-or-later; the WebSocket client is MIT. | `pulsar-multi-stream` | `pulsar-multi-stream.dll` | Destination registry, shared-encoder fan-out, adaptive bitrate, capabilities, audio/monitoring/diagnostic vendor requests. Owns vendor `pulsar`. | | `pulsar-scene-source` | `pulsar-scene-source.dll` | Legacy single browser-capture replacement. Owns vendor `pulsar-scene`. | | `pulsar-browser` | `pulsar-browser.dll` + `pulsar-browser-page.exe` | CEF browser source, accelerated/software rendering callbacks, source-task lifecycle and browser shutdown fence. Full distribution only. | +| `pulsar-camera-effects` | `pulsar-camera-effects.dll` | Embedded GPU source vignette, bounded settings, alpha preservation and bypass. No AI models or new capture producer. | | `pulsar-output-classify` | Header-only interface target | Shared stable output-failure classification used by frontend and registry. | | `pulsar-nv-secure-load` | Header-only interface target | Shared validated SDK-directory/loading policy used by upstream effect module, capability probe and native tests. | | Patched OBS | `obs.dll`, graphics and upstream module binaries | Rendering/audio/video I/O, atomic view swap, encoders, output interleaving, capture and return transport. | diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T082800Z-vignette-native.json b/evidence/local-20261009-capability-audit/eleven/20261010T082800Z-vignette-native.json new file mode 100644 index 00000000..c1c0712d --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T082800Z-vignette-native.json @@ -0,0 +1,793 @@ +{ + "started": 1791620888.8820999, + "mode": "isolated synthetic camera-effect regression", + "capabilities": { + "active_encoder": "nvenc", + "audio_bitrate": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ], + "capabilities": { + "active_encoder": { + "applicability": "boot-fixed", + "value": "nvenc" + }, + "audio_bitrate": { + "applicability": "live", + "max": 512, + "min": 64, + "step": 32, + "values": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ] + }, + "audio_monitoring": { + "applicability": "live", + "available": true, + "device_bound": true, + "device_id": "{0.0.0.00000000}.{3a9b3ef5-04ae-4b95-88d7-53e24294bc68}", + "device_name": "Default", + "device_selectable": true + }, + "audio_sample_rate": { + "applicability": "read-only", + "hz": 48000 + }, + "audio_speaker_layout": { + "applicability": "read-only", + "channels": 2, + "layout": "stereo" + }, + "audio_tracks": { + "applicability": "read-only", + "bound": 1, + "count": 6, + "tracks": [ + { + "value": 1 + } + ] + }, + "destination_kinds": { + "applicability": "live", + "values": [ + { + "value": "rtmp_custom" + }, + { + "value": "vod_local" + }, + { + "value": "twitch" + }, + { + "value": "youtube" + } + ] + }, + "encoder_families": { + "applicability": "boot-fixed", + "values": [ + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 20, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "ultrafast" + }, + { + "value": "superfast" + }, + { + "value": "veryfast" + }, + { + "value": "faster" + }, + { + "value": "fast" + }, + { + "value": "medium" + }, + { + "value": "slow" + }, + { + "value": "slower" + }, + { + "value": "veryslow" + }, + { + "value": "placebo" + } + ], + "profiles": [ + { + "value": "baseline" + }, + { + "value": "main" + }, + { + "value": "high" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "VBR" + } + ], + "value": "x264" + }, + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 10, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "p1" + }, + { + "value": "p2" + }, + { + "value": "p3" + }, + { + "value": "p4" + }, + { + "value": "p5" + }, + { + "value": "p6" + }, + { + "value": "p7" + } + ], + "profiles": [ + { + "value": "high" + }, + { + "value": "main" + }, + { + "value": "baseline" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "CQP" + }, + { + "value": "VBR" + } + ], + "value": "nvenc" + } + ] + }, + "encoders": { + "applicability": "boot-fixed", + "values": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ] + }, + "filters": { + "applicability": "live", + "values": [ + { + "value": "mask_filter" + }, + { + "value": "mask_filter_v2" + }, + { + "value": "crop_filter" + }, + { + "value": "gain_filter" + }, + { + "value": "basic_eq_filter" + }, + { + "value": "hdr_tonemap_filter" + }, + { + "value": "color_filter" + }, + { + "value": "color_filter_v2" + }, + { + "value": "scale_filter" + }, + { + "value": "scroll_filter" + }, + { + "value": "gpu_delay" + }, + { + "value": "color_key_filter" + }, + { + "value": "color_key_filter_v2" + }, + { + "value": "clut_filter" + }, + { + "value": "sharpness_filter" + }, + { + "value": "sharpness_filter_v2" + }, + { + "value": "chroma_key_filter" + }, + { + "value": "chroma_key_filter_v2" + }, + { + "value": "async_delay_filter" + }, + { + "value": "noise_suppress_filter" + }, + { + "value": "noise_suppress_filter_v2" + }, + { + "value": "invert_polarity_filter" + }, + { + "value": "noise_gate_filter" + }, + { + "value": "compressor_filter" + }, + { + "value": "limiter_filter" + }, + { + "value": "expander_filter" + }, + { + "value": "upward_compressor_filter" + }, + { + "value": "luma_key_filter" + }, + { + "value": "luma_key_filter_v2" + }, + { + "value": "pulsar_vignette_filter" + } + ] + }, + "graphics_adapters": { + "active_index": 0, + "applicability": "read-only", + "values": [ + { + "index": 0, + "value": "NVIDIA GeForce RTX 3070" + } + ] + }, + "nv_filters": { + "afx": { + "directory_designated": false, + "dlls_present": false, + "min_version": "1.6.1.2", + "models_present": false, + "usable": false + }, + "applicability": "read-only", + "module_loaded": false, + "vfx": { + "directory_designated": false, + "dlls_present": false, + "min_version": "0.7.6.0", + "models_present": false, + "usable": false + } + }, + "output_scales": { + "applicability": "boot-fixed", + "canvas": { + "height": 1080, + "width": 1920 + }, + "values": [ + { + "height": 1080, + "scale": 1.0, + "value": "1920x1080", + "width": 1920 + } + ] + }, + "record_container": { + "applicability": "boot-fixed", + "value": "mp4", + "values": [ + { + "value": "mp4" + }, + { + "value": "mkv" + } + ] + }, + "record_markers": { + "add_chapter": false, + "applicability": "read-only", + "split_file": true + }, + "source_kinds": { + "applicability": "live", + "values": [ + { + "value": "audio_line" + }, + { + "value": "pulsar_preview_frame" + }, + { + "value": "image_source" + }, + { + "value": "color_source" + }, + { + "value": "color_source_v2" + }, + { + "value": "color_source_v3" + }, + { + "value": "slideshow" + }, + { + "value": "slideshow_v2" + }, + { + "value": "ffmpeg_source" + }, + { + "value": "text_gdiplus" + }, + { + "value": "text_gdiplus_v2" + }, + { + "value": "text_gdiplus_v3" + }, + { + "value": "browser_source" + }, + { + "value": "text_ft2_source" + }, + { + "value": "text_ft2_source_v2" + }, + { + "value": "vlc_source" + }, + { + "value": "monitor_capture" + }, + { + "value": "window_capture" + }, + { + "value": "game_capture" + }, + { + "value": "dshow_input" + }, + { + "value": "wasapi_input_capture" + }, + { + "value": "wasapi_output_capture" + }, + { + "value": "wasapi_process_output_capture" + } + ] + }, + "video_bitrate": { + "applicability": "live", + "max": 50000, + "min": 200, + "step": 50 + }, + "video_colorimetry": { + "applicability": "read-only", + "format": "NV12", + "range": "Partial", + "value": "709" + } + }, + "encoders": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ], + "version": 1, + "video_bitrate": { + "max": 50000, + "min": 200 + } + }, + "inputInventoryCount": 16, + "baseline": { + "center": [ + 255, + 255, + 255, + 255 + ], + "corner": [ + 255, + 255, + 255, + 255 + ] + }, + "vignette": { + "center": [ + 255, + 255, + 255, + 255 + ], + "corner": [ + 51, + 51, + 51, + 255 + ] + }, + "translucentAlpha": [ + 128, + 128 + ], + "keyedCenter": [ + 0, + 0, + 0, + 0 + ], + "performance": { + "fixture": "1920x1080 white native source; concurrent dev GPU load", + "bypass": { + "before": { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.350003, + "cpuUsage": 1.1316817236217571, + "memoryUsage": 97.38671875, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 173, + "webSocketSessionIncomingMessages": 30, + "webSocketSessionOutgoingMessages": 30 + }, + "samples": [ + { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.355645, + "cpuUsage": 0.19264057522167538, + "memoryUsage": 97.6015625, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 233, + "webSocketSessionIncomingMessages": 31, + "webSocketSessionOutgoingMessages": 31 + }, + { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.354549, + "cpuUsage": 0.4847241984147729, + "memoryUsage": 97.81640625, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 294, + "webSocketSessionIncomingMessages": 32, + "webSocketSessionOutgoingMessages": 32 + }, + { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.372565, + "cpuUsage": 0.5789769236287504, + "memoryUsage": 98.03515625, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 355, + "webSocketSessionIncomingMessages": 33, + "webSocketSessionOutgoingMessages": 33 + }, + { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.353403, + "cpuUsage": 0.38676768848915993, + "memoryUsage": 98.25, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 415, + "webSocketSessionIncomingMessages": 34, + "webSocketSessionOutgoingMessages": 34 + }, + { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.329988, + "cpuUsage": 0.3862124455669089, + "memoryUsage": 98.4609375, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 476, + "webSocketSessionIncomingMessages": 35, + "webSocketSessionOutgoingMessages": 35 + } + ], + "frameDelta": 303, + "skippedDelta": 0, + "meanRenderMs": 0.35323000000000004 + }, + "vignette": { + "before": { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.560757, + "cpuUsage": 0.5356477529764532, + "memoryUsage": 98.87109375, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 596, + "webSocketSessionIncomingMessages": 37, + "webSocketSessionOutgoingMessages": 37 + }, + "samples": [ + { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.38528, + "cpuUsage": 0.38736485986639246, + "memoryUsage": 98.87109375, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 657, + "webSocketSessionIncomingMessages": 38, + "webSocketSessionOutgoingMessages": 38 + }, + { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.429996, + "cpuUsage": 0.3885995414699503, + "memoryUsage": 98.87109375, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 717, + "webSocketSessionIncomingMessages": 39, + "webSocketSessionOutgoingMessages": 39 + }, + { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.447819, + "cpuUsage": 0.386274124147836, + "memoryUsage": 98.87109375, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 778, + "webSocketSessionIncomingMessages": 40, + "webSocketSessionOutgoingMessages": 40 + }, + { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.449339, + "cpuUsage": 0.48819591335434565, + "memoryUsage": 98.87109375, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 838, + "webSocketSessionIncomingMessages": 41, + "webSocketSessionOutgoingMessages": 41 + }, + { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.429172, + "cpuUsage": 0.5781461709419183, + "memoryUsage": 98.87109375, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 899, + "webSocketSessionIncomingMessages": 42, + "webSocketSessionOutgoingMessages": 42 + } + ], + "frameDelta": 303, + "skippedDelta": 0, + "meanRenderMs": 0.42832119999999996 + } + }, + "checks": [ + "registration", + "pixel falloff", + "opaque alpha", + "partial alpha", + "native bypass", + "zero intensity", + "live update", + "filter stack", + "transparent center", + "1080p realtime", + "removal" + ], + "stats": { + "activeFps": 60.0000024000001, + "availableDiskSpace": 116547.265625, + "averageFrameRenderTime": 0.429172, + "cpuUsage": 65.0044931105638, + "memoryUsage": 98.875, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 899, + "webSocketSessionIncomingMessages": 45, + "webSocketSessionOutgoingMessages": 45 + }, + "shutdown": { + "exitCode": 0, + "forced": false + }, + "stagingErrors": 0, + "passed": true +} diff --git a/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T084713Z-feature-report.md b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T084713Z-feature-report.md new file mode 100644 index 00000000..264e1fda --- /dev/null +++ b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T084713Z-feature-report.md @@ -0,0 +1,13 @@ +# Native camera-effects handoff + +Worktree : Pulsar/.worktrees/eleven-local-20261009-capability-audit. Branche : eleven/local-20261009-capability-audit. Base : c05dca2627afbfa42b3cc8bb5e830432147c390f. + +Ajout autonome : plugins/pulsar-camera-effects, option CMake PULSAR_BUILD_CAMERA_EFFECTS ON, documentation, probe-camera-effects.py. Aucun patch libobs, CEF ou capture-effects. Le shader embarqué preserve alpha et n'ouvre pas de producteur de capture. Packaging utilise le staging existant de tous les modules. + +Build : CMake/Visual Studio 17 2022 x64 RelWithDebInfo, headers libobs canonique en lecture seule et import library de .cache/upstream-build. DLL SHA256 80D778EA19654EB131F7743A3626381E00F6E80948B6022CA0C7DA5BBB79610D. + +Preuve native finale : evidence/local-20261009-capability-audit/eleven/20261010T082800Z-vignette-native.json. Registration/manifeste, pixels RGB, alpha 0/128/255, paramètres live, bypass/amount zero, empilement chroma et retrait passent. À 1080p60 : bypass 0,353230 ms, effet 0,428321 ms moyens ; 303 frames/5s par mode, zéro frame de rendu sautée. Source blanche synthétique, pas de vrai périphérique/Live/enregistrement ; charge GPU concurrente, mesure indicative. Arrêt code 0 sans kill forcé, zéro erreur staging. + +Deux premières sondes sont conservées : la première lisait le pixel immédiatement après la commande avant le tick vidéo et échouait. La sonde attend maintenant une frame conforme avec délai borné. Cela ne prouve pas un effet NVIDIA IA : les SDK/modèles sont absents. + +Le runtime commun et les worktrees étrangers restent préservés, ainsi que le .cache préexistant non suivi. Intégration proposée seulement du nouveau module, CMake et documentation ; conserver le module capture-effects du coordinateur. Synthèse transverse dans le rapport feature-report de Prism, même dossier de preuve. diff --git a/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T090000Z-vignette-native.json b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T090000Z-vignette-native.json new file mode 100644 index 00000000..20a8e0c0 --- /dev/null +++ b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T090000Z-vignette-native.json @@ -0,0 +1,43 @@ +{ + "started": 1791619565.0414534, + "mode": "isolated synthetic camera-effect regression", + "inputInventoryCount": 16, + "baseline": { + "center": [ + 255, + 255, + 255, + 255 + ], + "corner": [ + 255, + 255, + 255, + 255 + ] + }, + "vignette": { + "center": [ + 255, + 255, + 255, + 255 + ], + "corner": [ + 51, + 51, + 51, + 255 + ] + }, + "failure": { + "type": "AssertionError", + "message": "" + }, + "shutdown": { + "exitCode": 0, + "forced": false + }, + "stagingErrors": 0, + "passed": false +} diff --git a/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T090500Z-vignette-native.json b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T090500Z-vignette-native.json new file mode 100644 index 00000000..ba896895 --- /dev/null +++ b/evidence/local-20261010-nvidia-broadcast-audit/eleven/20261010T090500Z-vignette-native.json @@ -0,0 +1,69 @@ +{ + "started": 1791619606.1370175, + "mode": "isolated synthetic camera-effect regression", + "inputInventoryCount": 16, + "baseline": { + "center": [ + 255, + 255, + 255, + 255 + ], + "corner": [ + 255, + 255, + 255, + 255 + ] + }, + "vignette": { + "center": [ + 255, + 255, + 255, + 255 + ], + "corner": [ + 51, + 51, + 51, + 255 + ] + }, + "keyedCenter": [ + 0, + 0, + 0, + 0 + ], + "checks": [ + "registration", + "pixel falloff", + "opaque alpha", + "native bypass", + "zero intensity", + "live update", + "filter stack", + "transparent center", + "removal" + ], + "stats": { + "activeFps": 0.0, + "availableDiskSpace": 116569.078125, + "averageFrameRenderTime": 0.0, + "cpuUsage": 4.318120284665795, + "memoryUsage": 97.70703125, + "outputSkippedFrames": 0, + "outputTotalFrames": 0, + "renderSkippedFrames": 0, + "renderTotalFrames": 38, + "webSocketSessionIncomingMessages": 23, + "webSocketSessionOutgoingMessages": 23 + }, + "shutdown": { + "exitCode": 0, + "forced": false + }, + "stagingErrors": 0, + "passed": true +} diff --git a/plugins/pulsar-camera-effects/CMakeLists.txt b/plugins/pulsar-camera-effects/CMakeLists.txt new file mode 100644 index 00000000..0435dea7 --- /dev/null +++ b/plugins/pulsar-camera-effects/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.28) +project(pulsar-camera-effects LANGUAGES CXX) + +set(PULSAR_CAMERA_LIBOBS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../upstream/libobs" CACHE PATH "libobs source headers") +if(NOT DEFINED PULSAR_UPSTREAM_BUILD_DIR) + set(PULSAR_UPSTREAM_BUILD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../upstream/build_x64") +endif() +add_library(pulsar-camera-effects MODULE camera-effects.cpp) +target_compile_features(pulsar-camera-effects PRIVATE cxx_std_17) +target_include_directories(pulsar-camera-effects PRIVATE + "${PULSAR_CAMERA_LIBOBS_DIR}" + "${PULSAR_UPSTREAM_BUILD_DIR}/libobs" + "${PULSAR_UPSTREAM_BUILD_DIR}/config") +target_link_libraries(pulsar-camera-effects PRIVATE + "${PULSAR_UPSTREAM_BUILD_DIR}/libobs/RelWithDebInfo/obs.lib") +set_target_properties(pulsar-camera-effects PROPERTIES PREFIX "" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO "${PULSAR_UPSTREAM_BUILD_DIR}/rundir/RelWithDebInfo/obs-plugins/64bit" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${PULSAR_UPSTREAM_BUILD_DIR}/rundir/RelWithDebInfo/obs-plugins/64bit") diff --git a/plugins/pulsar-camera-effects/README.md b/plugins/pulsar-camera-effects/README.md new file mode 100644 index 00000000..b5c173de --- /dev/null +++ b/plugins/pulsar-camera-effects/README.md @@ -0,0 +1,20 @@ +# Native camera effects + +`camera-effects.cpp` registers `pulsar_vignette_filter`: a GPU vignette applied to +the native source, shared by Preview and Program. It darkens RGB towards the +edges and preserves source alpha. This is a graphic effect, without an AI model. + +Settings: `amount` 0–1, `radius` 0.1–1.5, `softness` 0.01–1. Defaults are +0.35, 0.65 and 0.5. Amount zero and native disable bypass the effect. Parameters +are finite/clamped in native code; Prism validates before sending them. +The shader is embedded. No file/shader path, CPU readback or additional capture +producer is introduced. Effect allocation/destruction stays on the graphics thread. + +The top-level CMake build includes this module in both distribution variants. +Standalone builds accept libobs source-header and build-directory paths. Packaging +uses the existing plugin-directory staging. The capability inventory reports the +registration; hosts must gate the controls on that inventory. + +Regression: `scripts/probe-camera-effects.py` exercises actual native pixels, +transparency, parameter updates, stack/bypass/removal and graceful shutdown without +physical camera, broadcasting or recording. diff --git a/plugins/pulsar-camera-effects/camera-effects.cpp b/plugins/pulsar-camera-effects/camera-effects.cpp new file mode 100644 index 00000000..32b18ce0 --- /dev/null +++ b/plugins/pulsar-camera-effects/camera-effects.cpp @@ -0,0 +1,114 @@ +#include +#include +#include + +OBS_DECLARE_MODULE() +MODULE_EXPORT const char *obs_module_description(void) +{ + return "Pulsar native camera vignette"; +} + +// Embedded shader: no caller-controlled file, shader or resource path. +static const char *shader = R"( +uniform float4x4 ViewProj; +uniform texture2d image; +uniform float amount; +uniform float radius; +uniform float softness; +sampler_state linearSampler { Filter = Linear; AddressU = Clamp; AddressV = Clamp; }; +struct Vertex { float4 pos : POSITION; float2 uv : TEXCOORD0; }; +Vertex VSDefault(Vertex v) { v.pos = mul(float4(v.pos.xyz, 1.0), ViewProj); return v; } +float4 PSVignette(Vertex v) : TARGET { + float4 color = image.Sample(linearSampler, v.uv); + float distance = length((v.uv - float2(0.5, 0.5)) * 2.0); + float shade = smoothstep(radius, radius + softness, distance); + color.rgb *= 1.0 - amount * shade; + return color; +} +technique Draw { pass { vertex_shader = VSDefault(v); pixel_shader = PSVignette(v); } } +)"; + +struct Vignette { + obs_source_t *source; + gs_effect_t *effect; + float amount, radius, softness; +}; + +static const char *name(void *) { return "Vignette"; } +static float bounded(obs_data_t *settings, const char *key, float fallback, float lo, float hi) +{ + double value = obs_data_get_double(settings, key); + return std::isfinite(value) ? std::clamp(static_cast(value), lo, hi) : fallback; +} +static void update(void *data, obs_data_t *settings) +{ + auto *filter = static_cast(data); + filter->amount = bounded(settings, "amount", .35f, 0.f, 1.f); + filter->radius = bounded(settings, "radius", .65f, .1f, 1.5f); + filter->softness = bounded(settings, "softness", .5f, .01f, 1.f); +} +static void *create(obs_data_t *settings, obs_source_t *source) +{ + auto *filter = new Vignette{source, nullptr, 0, 0, 0}; + obs_enter_graphics(); + filter->effect = gs_effect_create(shader, "pulsar-vignette", nullptr); + obs_leave_graphics(); + if (!filter->effect) { + delete filter; + return nullptr; + } + update(filter, settings); + return filter; +} +static void destroy(void *data) +{ + auto *filter = static_cast(data); + obs_enter_graphics(); + gs_effect_destroy(filter->effect); + obs_leave_graphics(); + delete filter; +} +static void render(void *data, gs_effect_t *) +{ + auto *filter = static_cast(data); + if (filter->amount == 0.f) { + obs_source_skip_video_filter(filter->source); + return; + } + if (!obs_source_process_filter_begin(filter->source, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) + return; + gs_effect_set_float(gs_effect_get_param_by_name(filter->effect, "amount"), filter->amount); + gs_effect_set_float(gs_effect_get_param_by_name(filter->effect, "radius"), filter->radius); + gs_effect_set_float(gs_effect_get_param_by_name(filter->effect, "softness"), filter->softness); + obs_source_process_filter_end(filter->source, filter->effect, 0, 0); +} +static void defaults(obs_data_t *settings) +{ + obs_data_set_default_double(settings, "amount", .35); + obs_data_set_default_double(settings, "radius", .65); + obs_data_set_default_double(settings, "softness", .5); +} +static obs_properties_t *properties(void *) +{ + auto *props = obs_properties_create(); + obs_properties_add_float_slider(props, "amount", "Intensity", 0, 1, .01); + obs_properties_add_float_slider(props, "radius", "Clear center", .1, 1.5, .01); + obs_properties_add_float_slider(props, "softness", "Edge softness", .01, 1, .01); + return props; +} +bool obs_module_load(void) +{ + obs_source_info info{}; + info.id = "pulsar_vignette_filter"; + info.type = OBS_SOURCE_TYPE_FILTER; + info.output_flags = OBS_SOURCE_VIDEO; + info.get_name = name; + info.create = create; + info.destroy = destroy; + info.update = update; + info.video_render = render; + info.get_defaults = defaults; + info.get_properties = properties; + obs_register_source(&info); + return true; +} diff --git a/scripts/probe-camera-effects.py b/scripts/probe-camera-effects.py new file mode 100644 index 00000000..945f8701 --- /dev/null +++ b/scripts/probe-camera-effects.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 +"""Native vignette pixels and lifecycle. Synthetic inputs, no camera/live/recording.""" +import argparse +import asyncio +import base64 +import hashlib +import importlib.util +import io +import json +import os +import sys +import time +from pathlib import Path +from PIL import Image +import websockets + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location("camera_effects_transport", ROOT / "scripts/probe-configurable-transition.py") +transport = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = transport +spec.loader.exec_module(transport) + +async def exercise(proc, report): + proc.spawn() + await asyncio.to_thread(proc.wait_for, transport.process.READY_RE, 45) + await asyncio.to_thread(proc.wait_for_shutdown_control_ready, 10) + async with websockets.connect(f"ws://127.0.0.1:{proc.port}", max_size=8*1024*1024) as ws: + hello = json.loads(await ws.recv())["d"] + identify = {"rpcVersion": 1, "eventSubscriptions": 0} + if "authentication" in hello: + auth = hello["authentication"] + secret = base64.b64encode(hashlib.sha256((proc.password + auth["salt"]).encode()).digest()).decode() + identify["authentication"] = base64.b64encode(hashlib.sha256((secret + auth["challenge"]).encode()).digest()).decode() + await ws.send(json.dumps({"op": 1, "d": identify})) + assert json.loads(await ws.recv())["op"] == 2 + wire = transport.Wire(ws) + capabilities = await wire.vendor("GetCapabilities", vendor="pulsar") + report["capabilities"] = capabilities + kinds = (await wire.call("GetInputKindList"))["inputKinds"] + # Filter registration is checked by actual creation, never inferred from DLL presence. + report["inputInventoryCount"] = len(kinds) + await wire.call("CreateScene", {"sceneName": "CameraEffectsRegression"}) + await wire.call("CreateInput", {"sceneName": "CameraEffectsRegression", "inputName": "WhiteFixture", "inputKind": "color_source_v3", "inputSettings": {"color": 0xffffffff, "width": 128, "height": 72}, "sceneItemEnabled": True}) + await wire.call("SetCurrentProgramScene", {"sceneName": "CameraEffectsRegression"}) + async def image(): + result = await wire.call("GetSourceScreenshot", {"sourceName": "WhiteFixture", "imageFormat": "png", "imageWidth": 128, "imageHeight": 72}) + return Image.open(io.BytesIO(base64.b64decode(result["imageData"].split(",", 1)[1]))).convert("RGBA") + async def frame_matching(predicate): + deadline = time.monotonic() + 2 + while True: + frame = await image() + if predicate(frame): + return frame + if time.monotonic() >= deadline: + raise AssertionError("Native frame did not reach the requested effect state") + await asyncio.sleep(.05) + baseline = await image() + report["baseline"] = {"center": list(baseline.getpixel((64, 36))), "corner": list(baseline.getpixel((0, 0)))} + await wire.call("CreateSourceFilter", {"sourceName": "WhiteFixture", "filterName": "audit/vignette", "filterKind": "pulsar_vignette_filter", "filterSettings": {"amount": .8, "radius": .65, "softness": .5}}) + actual = await wire.call("GetSourceFilter", {"sourceName": "WhiteFixture", "filterName": "audit/vignette"}) + assert actual["filterKind"] == "pulsar_vignette_filter" + assert actual["filterSettings"]["amount"] == .8 + vignette = await frame_matching(lambda frame: frame.getpixel((0, 0))[0] < 100) + center, corner = vignette.getpixel((64, 36)), vignette.getpixel((0, 0)) + assert center[0] >= 250 and 40 <= corner[0] <= 65, (center, corner) + assert vignette.getchannel("A").getextrema() == (255, 255) + report["vignette"] = {"center": list(center), "corner": list(corner)} + # Semi-transparent inputs retain alpha, not only fully opaque/keyed pixels. + await wire.call("SetInputSettings", {"inputName": "WhiteFixture", "inputSettings": {"color": 0x80ffffff}, "overlay": True}) + translucent = await frame_matching(lambda frame: frame.getpixel((64, 36))[3] == 128) + assert translucent.getchannel("A").getextrema() == (128, 128) + report["translucentAlpha"] = list(translucent.getchannel("A").getextrema()) + await wire.call("SetInputSettings", {"inputName": "WhiteFixture", "inputSettings": {"color": 0xffffffff}, "overlay": True}) + await wire.call("SetSourceFilterEnabled", {"sourceName": "WhiteFixture", "filterName": "audit/vignette", "filterEnabled": False}) + await frame_matching(lambda frame: frame.tobytes() == baseline.tobytes()) + await wire.call("SetSourceFilterEnabled", {"sourceName": "WhiteFixture", "filterName": "audit/vignette", "filterEnabled": True}) + await wire.call("SetSourceFilterSettings", {"sourceName": "WhiteFixture", "filterName": "audit/vignette", "filterSettings": {"amount": 0}, "overlay": True}) + await frame_matching(lambda frame: frame.tobytes() == baseline.tobytes()) + # Stack and alpha: chroma fully keys the green center. Vignette cannot restore alpha. + await wire.call("SetInputSettings", {"inputName": "WhiteFixture", "inputSettings": {"color": 0xff00ff00}, "overlay": True}) + await wire.call("CreateSourceFilter", {"sourceName": "WhiteFixture", "filterName": "audit/key", "filterKind": "chroma_key_filter_v2", "filterSettings": {"key_color_type": "green", "similarity": 400}}) + await wire.call("SetSourceFilterSettings", {"sourceName": "WhiteFixture", "filterName": "audit/vignette", "filterSettings": {"amount": .8}, "overlay": True}) + keyed = await frame_matching(lambda frame: frame.getpixel((64, 36))[3] == 0) + assert keyed.getpixel((64, 36))[3] == 0 + report["keyedCenter"] = list(keyed.getpixel((64, 36))) + await wire.call("RemoveSourceFilter", {"sourceName": "WhiteFixture", "filterName": "audit/key"}) + await wire.call("SetInputSettings", {"inputName": "WhiteFixture", "inputSettings": {"color": 0xffffffff, "width": 1920, "height": 1080}, "overlay": True}) + async def measure(enabled): + await wire.call("SetSourceFilterEnabled", {"sourceName": "WhiteFixture", "filterName": "audit/vignette", "filterEnabled": enabled}) + await asyncio.sleep(2) + before = await wire.call("GetStats") + samples = [] + for _ in range(5): + await asyncio.sleep(1) + samples.append(await wire.call("GetStats")) + after = samples[-1] + return {"before": before, "samples": samples, + "frameDelta": after["renderTotalFrames"] - before["renderTotalFrames"], + "skippedDelta": after["renderSkippedFrames"] - before["renderSkippedFrames"], + "meanRenderMs": sum(row["averageFrameRenderTime"] for row in samples) / len(samples)} + report["performance"] = {"fixture": "1920x1080 white native source; concurrent dev GPU load", + "bypass": await measure(False), "vignette": await measure(True)} + for result in report["performance"]["bypass"], report["performance"]["vignette"]: + assert result["frameDelta"] >= 200, result + assert result["skippedDelta"] == 0, result + for name in ["audit/vignette"]: + await wire.call("RemoveSourceFilter", {"sourceName": "WhiteFixture", "filterName": name}) + assert (await wire.call("GetSourceFilterList", {"sourceName": "WhiteFixture"}))["filters"] == [] + report["checks"] = ["registration", "pixel falloff", "opaque alpha", "partial alpha", "native bypass", "zero intensity", "live update", "filter stack", "transparent center", "1080p realtime", "removal"] + report["stats"] = await wire.call("GetStats") + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--exe", type=Path, required=True) + parser.add_argument("--work", type=Path, required=True) + parser.add_argument("--report", type=Path, required=True) + args = parser.parse_args() + args.work.mkdir(parents=True, exist_ok=True) + os.environ["PULSAR_DIRECTSHOW_LEGACY_ALIAS"] = "0" + os.environ["PULSAR_LEGACY_ALIAS"] = "disabled" + proc = transport.process.PulsarProcess(args.exe.resolve(), "nvenc", args.work.resolve()) + report = {"started": time.time(), "mode": "isolated synthetic camera-effect regression"} + try: + asyncio.run(exercise(proc, report)) + except Exception as error: + report["failure"] = {"type": type(error).__name__, "message": str(error)} + raise + finally: + proc.shutdown() + report["shutdown"] = {"exitCode": proc.proc.poll(), "forced": proc.forced_kill_used} + report["stagingErrors"] = sum("Failed to create staging surface" in line for line in proc.snapshot()) + report["passed"] = "failure" not in report and report["shutdown"] == {"exitCode": 0, "forced": False} and report["stagingErrors"] == 0 + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text(json.dumps(report, indent=2) + "\n", encoding="utf8", newline="\n") + print(json.dumps(report)) + return 0 if report["passed"] else 1 + +if __name__ == "__main__": + sys.exit(main()) From c298c13ff36f80d64491397a3feb2e77fb0ae127 Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Sat, 10 Oct 2026 12:00:19 +0200 Subject: [PATCH 10/17] test(nvidia): qualify installed effects with synthetic native media Exercise all registered NVIDIA effects with isolated pixel, bypass, audio and real-time checks. Preserve SDK diagnostics and artifact identity separately from functional success. No SDK payload is redistributed. Agent-Role: Eleven Work-Unit: local-20261009-capability-audit --- .../eleven/20261010T100000Z-native.json | 674 ++++++++++++++++++ .../eleven/20261010T100000Z-report.md | 34 + plugins/pulsar-nv-secure-load/README.md | 21 + scripts/probe-nvidia-effects.py | 151 ++++ 4 files changed, 880 insertions(+) create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T100000Z-native.json create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T100000Z-report.md create mode 100644 scripts/probe-nvidia-effects.py diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T100000Z-native.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T100000Z-native.json new file mode 100644 index 00000000..25ad48af --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T100000Z-native.json @@ -0,0 +1,674 @@ +{ + "started": 1791626219.8804297, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "capabilities": { + "active_encoder": "nvenc", + "audio_bitrate": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ], + "capabilities": { + "active_encoder": { + "applicability": "boot-fixed", + "value": "nvenc" + }, + "audio_bitrate": { + "applicability": "live", + "max": 512, + "min": 64, + "step": 32, + "values": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ] + }, + "audio_monitoring": { + "applicability": "live", + "available": true, + "device_bound": true, + "device_id": "{0.0.0.00000000}.{3a9b3ef5-04ae-4b95-88d7-53e24294bc68}", + "device_name": "Default", + "device_selectable": true + }, + "audio_sample_rate": { + "applicability": "read-only", + "hz": 48000 + }, + "audio_speaker_layout": { + "applicability": "read-only", + "channels": 2, + "layout": "stereo" + }, + "audio_tracks": { + "applicability": "read-only", + "bound": 1, + "count": 6, + "tracks": [ + { + "value": 1 + } + ] + }, + "destination_kinds": { + "applicability": "live", + "values": [ + { + "value": "rtmp_custom" + }, + { + "value": "vod_local" + }, + { + "value": "twitch" + }, + { + "value": "youtube" + } + ] + }, + "encoder_families": { + "applicability": "boot-fixed", + "values": [ + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 20, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "ultrafast" + }, + { + "value": "superfast" + }, + { + "value": "veryfast" + }, + { + "value": "faster" + }, + { + "value": "fast" + }, + { + "value": "medium" + }, + { + "value": "slow" + }, + { + "value": "slower" + }, + { + "value": "veryslow" + }, + { + "value": "placebo" + } + ], + "profiles": [ + { + "value": "baseline" + }, + { + "value": "main" + }, + { + "value": "high" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "VBR" + } + ], + "value": "x264" + }, + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 10, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "p1" + }, + { + "value": "p2" + }, + { + "value": "p3" + }, + { + "value": "p4" + }, + { + "value": "p5" + }, + { + "value": "p6" + }, + { + "value": "p7" + } + ], + "profiles": [ + { + "value": "high" + }, + { + "value": "main" + }, + { + "value": "baseline" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "CQP" + }, + { + "value": "VBR" + } + ], + "value": "nvenc" + } + ] + }, + "encoders": { + "applicability": "boot-fixed", + "values": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ] + }, + "filters": { + "applicability": "live", + "values": [ + { + "value": "nvidia_audiofx_filter" + }, + { + "value": "nv_greenscreen_filter" + }, + { + "value": "nv_blur_filter" + }, + { + "value": "nv_background_blur_filter" + }, + { + "value": "mask_filter" + }, + { + "value": "mask_filter_v2" + }, + { + "value": "crop_filter" + }, + { + "value": "gain_filter" + }, + { + "value": "basic_eq_filter" + }, + { + "value": "hdr_tonemap_filter" + }, + { + "value": "color_filter" + }, + { + "value": "color_filter_v2" + }, + { + "value": "scale_filter" + }, + { + "value": "scroll_filter" + }, + { + "value": "gpu_delay" + }, + { + "value": "color_key_filter" + }, + { + "value": "color_key_filter_v2" + }, + { + "value": "clut_filter" + }, + { + "value": "sharpness_filter" + }, + { + "value": "sharpness_filter_v2" + }, + { + "value": "chroma_key_filter" + }, + { + "value": "chroma_key_filter_v2" + }, + { + "value": "async_delay_filter" + }, + { + "value": "noise_suppress_filter" + }, + { + "value": "noise_suppress_filter_v2" + }, + { + "value": "invert_polarity_filter" + }, + { + "value": "noise_gate_filter" + }, + { + "value": "compressor_filter" + }, + { + "value": "limiter_filter" + }, + { + "value": "expander_filter" + }, + { + "value": "upward_compressor_filter" + }, + { + "value": "luma_key_filter" + }, + { + "value": "luma_key_filter_v2" + }, + { + "value": "pulsar_vignette_filter" + } + ] + }, + "graphics_adapters": { + "active_index": 0, + "applicability": "read-only", + "values": [ + { + "index": 0, + "value": "NVIDIA GeForce RTX 3070" + } + ] + }, + "nv_filters": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "module_loaded": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "output_scales": { + "applicability": "boot-fixed", + "canvas": { + "height": 1080, + "width": 1920 + }, + "values": [ + { + "height": 1080, + "scale": 1.0, + "value": "1920x1080", + "width": 1920 + } + ] + }, + "record_container": { + "applicability": "boot-fixed", + "value": "mp4", + "values": [ + { + "value": "mp4" + }, + { + "value": "mkv" + } + ] + }, + "record_markers": { + "add_chapter": false, + "applicability": "read-only", + "split_file": true + }, + "source_kinds": { + "applicability": "live", + "values": [ + { + "value": "audio_line" + }, + { + "value": "pulsar_preview_frame" + }, + { + "value": "image_source" + }, + { + "value": "color_source" + }, + { + "value": "color_source_v2" + }, + { + "value": "color_source_v3" + }, + { + "value": "slideshow" + }, + { + "value": "slideshow_v2" + }, + { + "value": "ffmpeg_source" + }, + { + "value": "text_gdiplus" + }, + { + "value": "text_gdiplus_v2" + }, + { + "value": "text_gdiplus_v3" + }, + { + "value": "browser_source" + }, + { + "value": "text_ft2_source" + }, + { + "value": "text_ft2_source_v2" + }, + { + "value": "vlc_source" + }, + { + "value": "monitor_capture" + }, + { + "value": "window_capture" + }, + { + "value": "game_capture" + }, + { + "value": "dshow_input" + }, + { + "value": "wasapi_input_capture" + }, + { + "value": "wasapi_output_capture" + }, + { + "value": "wasapi_process_output_capture" + } + ] + }, + "video_bitrate": { + "applicability": "live", + "max": 50000, + "min": 200, + "step": 50 + }, + "video_colorimetry": { + "applicability": "read-only", + "format": "NV12", + "range": "Partial", + "value": "709" + } + }, + "encoders": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ], + "version": 1, + "video_bitrate": { + "max": 50000, + "min": 200 + } + }, + "video": [ + { + "kind": "nv_greenscreen_filter", + "settings": { + "mode": 1, + "processing_interval": 1, + "threshold": 0.8 + }, + "pixelDifferenceMean": 137.625, + "alphaRange": [ + 0, + 0 + ], + "frameDelta": 303, + "skippedDelta": 0, + "meanRenderMs": 5.056136199999999 + }, + { + "kind": "nv_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 302, + "skippedDelta": 0, + "meanRenderMs": 2.053671 + }, + { + "kind": "nv_background_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 302, + "skippedDelta": 0, + "meanRenderMs": 5.538079 + } + ], + "audio": { + "fixture": "seeded white noise, raw native volume-meter magnitude", + "baseline": 0.07979943847558538, + "methods": [ + { + "method": "denoiser", + "magnitude": 0.0049017984888764105 + }, + { + "method": "dereverb", + "magnitude": 0.018456676933315715 + }, + { + "method": "dereverb_denoiser", + "magnitude": 0.007230597041611293 + } + ] + }, + "checks": [ + "SDK usable/version", + "four filters registered", + "three VFX pixel changes and exact bypass", + "native render timings", + "three AFX model methods", + "noise suppression signal", + "filter removal" + ], + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T09:57:00Z INFO Myun0BgANXzHwjYF libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T09:57:00Z INFO Myun0BgANXzHwjYF libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T09:57:00Z INFO Myun0BgANXzHwjYF NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T09:57:00Z INFO Myun0BgANXzHwjYF NVIDIA Audio Effects: | enabled", + "2026-10-10T09:57:00Z INFO Myun0BgANXzHwjYF NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T09:57:00Z INFO Myun0BgANXzHwjYF NVIDIA VIDEO FX | : enabled, redistributable found", + "2026-10-10T09:57:11Z ERROR Myun0BgANXzHwjYF NVIDIA Video Effect: '[E | Missing src dst", + "2026-10-10T09:57:11Z ERROR Myun0BgANXzHwjYF NVIDIA Video Effect: '(null)' | ", + "2026-10-10T09:57:20Z ERROR Myun0BgANXzHwjYF NVIDIA Video Effect: '[E | Missing src dst", + "2026-10-10T09:57:20Z ERROR Myun0BgANXzHwjYF NVIDIA Video Effect: '(null)' | ", + "2026-10-10T09:57:31Z INFO Myun0BgANXzHwjYF NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T09:57:36Z INFO Myun0BgANXzHwjYF NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T09:57:36Z ERROR Myun0BgANXzHwjYF NVIDIA Audio Effects: Error - '(null)' | ", + "2026-10-10T09:57:41Z INFO Myun0BgANXzHwjYF NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T09:57:41Z ERROR Myun0BgANXzHwjYF NVIDIA Audio Effects: Error - '(null)' | ", + "2026-10-10T09:57:47Z ERROR Myun0BgANXzHwjYF NVIDIA Audio Effects: Error - '(null)' | ", + "2026-10-10T09:57:47Z ERROR Myun0BgANXzHwjYF NVIDIA Video Effect: '(null)' | " + ], + "sdkDiagnostics": [ + "2026-10-10T09:57:11Z ERROR Myun0BgANXzHwjYF NVIDIA Video Effect: '[E | Missing src dst", + "2026-10-10T09:57:11Z ERROR Myun0BgANXzHwjYF NVIDIA Video Effect: '(null)' | ", + "2026-10-10T09:57:20Z ERROR Myun0BgANXzHwjYF NVIDIA Video Effect: '[E | Missing src dst", + "2026-10-10T09:57:20Z ERROR Myun0BgANXzHwjYF NVIDIA Video Effect: '(null)' | ", + "2026-10-10T09:57:36Z ERROR Myun0BgANXzHwjYF NVIDIA Audio Effects: Error - '(null)' | ", + "2026-10-10T09:57:41Z ERROR Myun0BgANXzHwjYF NVIDIA Audio Effects: Error - '(null)' | ", + "2026-10-10T09:57:47Z ERROR Myun0BgANXzHwjYF NVIDIA Audio Effects: Error - '(null)' | ", + "2026-10-10T09:57:47Z ERROR Myun0BgANXzHwjYF NVIDIA Video Effect: '(null)' | " + ], + "qualification": "synthetic functional/performance regression; SDK diagnostics require separate review", + "passed": true, + "exeSha256": "FAC3466E90A744BFF14CA68DAC2967E5A51ECD610E9FCF1C1381F8B203299AC8", + "nvFiltersSha256": "C6F86E22BAED7566B5D391039E8B46B1F9BD94C7C701F719217934A204298BE6" +} diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T100000Z-report.md b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T100000Z-report.md new file mode 100644 index 00000000..5206137d --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T100000Z-report.md @@ -0,0 +1,34 @@ +# Installed NVIDIA effects regression + +The official Ampere AFX 1.6.1.2 and VFX 0.7.6.0 runtimes were installed as +external Program Files dependencies. This repository does not redistribute +their DLLs/models. Prism integration and installer provenance belong to +`Prism/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T100100Z-report.md`. + +The opt-in `scripts/probe-nvidia-effects.py` reuses existing isolated process, +authenticated WebSocket and graceful shutdown helpers. Native capability and +registration assertions, pixels, exact bypass, five-second frame/performance +windows and three audio processing methods passed against the existing isolated +audit runtime. Binary hashes identify the exact artifact; this probe does not +claim that an unbuilt source revision is the tested artifact. + +[Native evidence](20261010T100000Z-native.json) records all four registrations, +three measurable VFX changes, 302–303 rendered frames/measurement with zero +skips, mean render times 2.05–5.54 ms, denoiser noise reduction from magnitude +0.07980 to 0.00490, and normal process exit 0 without forced termination. +The combined audio method reached 0.00723; dereverb also produced processed +audio but its acoustic echo-removal quality is not established by white noise. + +SDK diagnostics are retained, including `Missing src dst` during video setup +and null logger callbacks during lifecycle. They are unresolved and explicitly +separate from the functional/performance pass. This is not evidence of zero +runtime errors, human portrait quality or real-room acoustics. The fixtures use +no physical capture, recording, network broadcast or speaker monitoring. + +The shared Prism integration owner independently verified the registered kinds, +creation/settings readback/removal, Preview and ProgramReturn; its proof remains +in `Prism/.worktrees/eleven-local-20261008-scene-sync/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T095635Z-checkpoint.json`. + +Packaging source checks pass and prohibit SDK/model payloads in product bundles. +Python syntax and whitespace checks pass. The probe and its nearest-owner README +are the only product/test changes here; native effect plugin code is unchanged. diff --git a/plugins/pulsar-nv-secure-load/README.md b/plugins/pulsar-nv-secure-load/README.md index 38598296..7f5a919f 100644 --- a/plugins/pulsar-nv-secure-load/README.md +++ b/plugins/pulsar-nv-secure-load/README.md @@ -73,3 +73,24 @@ state rather than inferring it from the launcher environment. NVENC encoding is a different module and does not depend on this effect-SDK loader. See [architecture](../../docs/ARCHITECTURE.md). + +## Installed SDK regression + +[`scripts/probe-nvidia-effects.py`](../../scripts/probe-nvidia-effects.py) is an +explicit hardware test for a machine with the official compatible AFX/VFX +redistributables installed. It takes `--exe`, `--work`, `--report`, `--afx` and +`--vfx` absolute paths. It reuses the existing authenticated wire and isolated +shutdown helper; the native loader still validates all designated paths. +Dependencies are Python, NumPy, Pillow and websockets. + +The fixture uses 1080p stripes and seeded 48 kHz noise: three video filters must +change native pixels, bypass must restore exact pixels, and each must sustain +at least 250 frames across five seconds with no skipped frames. Three audio +methods must load and process; denoising/combined must reduce the native meter +magnitude by at least half. Neither microphones, cameras, monitoring speakers, +recording nor network broadcast are used. The process must exit normally. + +Reports retain SDK warnings/errors separately: passing synthetic checks does +not certify the absence of SDK diagnostics, portrait segmentation quality, +room-echo removal quality, scene switches, or an external native output. SDK +packages/models remain external machine dependencies and are never committed. diff --git a/scripts/probe-nvidia-effects.py b/scripts/probe-nvidia-effects.py new file mode 100644 index 00000000..efc537b5 --- /dev/null +++ b/scripts/probe-nvidia-effects.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +"""Opt-in installed NVIDIA SDK regression. Synthetic video/audio; no devices or Live. + +Uses the isolated process and authenticated wire helpers from the transition +probe. SDK paths remain subject to the runtime's secure loader. Output paths +are explicit; no SDK binaries or models are bundled by this probe. +""" +import argparse +import asyncio +import base64 +import hashlib +import importlib.util +import io +import json +import os +from pathlib import Path +import sys +import time +import wave + +import numpy as np +from PIL import Image +import websockets + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location("nvidia_probe_transport", ROOT / "scripts/probe-configurable-transition.py") +transport = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = transport +spec.loader.exec_module(transport) +KINDS = ["nv_greenscreen_filter", "nv_blur_filter", "nv_background_blur_filter", "nvidia_audiofx_filter"] + +async def exercise(proc, work, report): + proc.spawn() + await asyncio.to_thread(proc.wait_for, transport.process.READY_RE, 45) + await asyncio.to_thread(proc.wait_for_shutdown_control_ready, 10) + async with websockets.connect(f"ws://127.0.0.1:{proc.port}", max_size=8*1024*1024) as ws: + hello = json.loads(await ws.recv())["d"] + identify = {"rpcVersion": 1, "eventSubscriptions": 65536} + if "authentication" in hello: + auth = hello["authentication"] + secret = base64.b64encode(hashlib.sha256((proc.password + auth["salt"]).encode()).digest()).decode() + identify["authentication"] = base64.b64encode(hashlib.sha256((secret + auth["challenge"]).encode()).digest()).decode() + await ws.send(json.dumps({"op": 1, "d": identify})) + assert json.loads(await ws.recv())["op"] == 2 + wire = transport.Wire(ws) + caps = await wire.vendor("GetCapabilities", vendor="pulsar") + report["capabilities"] = caps + inventory = {row["value"] for row in caps["capabilities"]["filters"]["values"]} + assert set(KINDS) <= inventory, inventory + nv = caps["capabilities"]["nv_filters"] + assert nv["afx"]["usable"] and nv["vfx"]["usable"], nv + await wire.call("CreateScene", {"sceneName": "NvidiaSDKRegression"}) + # Fine stripes make a spatial blur measurable; a non-person fixture + # makes the segmentation alpha measurable without biometric imagery. + pixels = np.full((1080, 1920, 3), 20, np.uint8) + pixels[:, ::8] = 240 + pixels[:, 1::8] = 240 + Image.fromarray(pixels).save(work / "stripes.png") + await wire.call("CreateInput", {"sceneName": "NvidiaSDKRegression", "inputName": "Stripes", "inputKind": "image_source", "inputSettings": {"file": str(work / "stripes.png")}, "sceneItemEnabled": True}) + await wire.call("SetCurrentProgramScene", {"sceneName": "NvidiaSDKRegression"}) + async def image(): + data = await wire.call("GetSourceScreenshot", {"sourceName": "Stripes", "imageFormat": "png", "imageWidth": 480, "imageHeight": 270}) + return np.asarray(Image.open(io.BytesIO(base64.b64decode(data["imageData"].split(",", 1)[1]))).convert("RGBA")) + await asyncio.sleep(1) + baseline = await image() + report["video"] = [] + for kind in KINDS[:3]: + settings = {"mode": 1, "threshold": .8, "processing_interval": 1} if kind == KINDS[0] else {"intensity": .8} + await wire.call("CreateSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/test", "filterKind": kind, "filterSettings": settings}) + actual = await wire.call("GetSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/test"}) + assert actual["filterKind"] == kind + await asyncio.sleep(3) + before = await wire.call("GetStats") + samples = [] + for _ in range(5): + await asyncio.sleep(1) + samples.append(await wire.call("GetStats")) + treated = await image() + row = {"kind": kind, "settings": actual["filterSettings"], "pixelDifferenceMean": float(np.abs(treated.astype(float)-baseline).mean()), "alphaRange": [int(treated[:,:,3].min()), int(treated[:,:,3].max())], "frameDelta": samples[-1]["renderTotalFrames"]-before["renderTotalFrames"], "skippedDelta": samples[-1]["renderSkippedFrames"]-before["renderSkippedFrames"], "meanRenderMs": sum(s["averageFrameRenderTime"] for s in samples)/len(samples)} + report["video"].append(row) + assert row["pixelDifferenceMean"] > 1, row + assert row["frameDelta"] >= 250 and row["skippedDelta"] == 0, row + await wire.call("SetSourceFilterEnabled", {"sourceName": "Stripes", "filterName": "sdk/test", "filterEnabled": False}) + await asyncio.sleep(.5) + assert np.array_equal(await image(), baseline), kind + await wire.call("RemoveSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/test"}) + rng = np.random.default_rng(20261010) + noise = (rng.normal(0, .08, 48000*12).clip(-1, 1)*32767).astype("", line) for line in proc.snapshot() if "NVIDIA" in line or "nvvfx" in line or "nvafx" in line] + report["sdkDiagnostics"] = [line for line in report["sdkLogs"] if " ERROR " in line or " WARN " in line] + report["qualification"] = "synthetic functional/performance regression; SDK diagnostics require separate review" + report["passed"] = "failure" not in report and report["shutdown"] == {"exitCode": 0, "forced": False} + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text(json.dumps(report, indent=2)+"\n", encoding="utf8", newline="\n") + print(json.dumps({key: value for key, value in report.items() if key not in ["capabilities", "sdkLogs"]})) + return 0 if report["passed"] else 1 + +if __name__ == "__main__": + sys.exit(main()) From 0cac057601e1836c57631d82cd563716569cec22 Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Sat, 10 Oct 2026 12:28:36 +0200 Subject: [PATCH 11/17] fix: qualify NVIDIA effects and guarantee zero intensity bypass --- README.md | 4 +- docs/LIBOBS-CHANGES.md | 30 ++++++- ...ia-effect-initialization-and-logging.patch | 82 +++++++++++++++++++ ...x-nvidia-audio-zero-intensity-bypass.patch | 65 +++++++++++++++ patches/README.md | 15 +++- scripts/probe-nvidia-effects.py | 81 ++++++++++++++++-- 6 files changed, 267 insertions(+), 10 deletions(-) create mode 100644 patches/0060-fix-nvidia-effect-initialization-and-logging.patch create mode 100644 patches/0061-fix-nvidia-audio-zero-intensity-bypass.patch diff --git a/README.md b/README.md index ddca2231..e8fa1690 100644 --- a/README.md +++ b/README.md @@ -473,7 +473,7 @@ does not recover scene identity from arbitrary pixels. Pulsar is a maintained native fork, not only a JavaScript wrapper around stock OBS. Its OBS submodule is pinned to `bd73b922891e56839b0bc86bdc519802802f9d68`, an OBS 32.1.2-derived revision. -Five foundational modifications are incorporated in that pin; **52 additional +Five foundational modifications are incorporated in that pin; **54 additional patch files** build the current media core. | Area | What Pulsar changes | @@ -598,7 +598,7 @@ as a troubleshooting shortcut. | [Client](packages/pulsar-client/README.md) | TypeScript API, typed events, errors and examples. | | [Embedding](docs/PRISM-EMBEDDING.md) | Host lifecycle, readiness and verified bundle integration. | | [Architecture](docs/ARCHITECTURE.md) | Runtime structure and ownership boundaries. | -| [All libobs changes](docs/LIBOBS-CHANGES.md) | Integrated OBS changes and all 52 source patches. | +| [All libobs changes](docs/LIBOBS-CHANGES.md) | Integrated OBS changes and all 54 source patches. | | [All documentation](docs/README.md) | Current guides, operational runbooks, contracts and historical studies. | | [Development](docs/DEVELOPMENT.md) | Build toolchain and local development. | | [Dual-lane canary](docs/runbooks/pulsar-dual-lane-canary.md) | Qualification and operational checks. | diff --git a/docs/LIBOBS-CHANGES.md b/docs/LIBOBS-CHANGES.md index 19c04d1c..9b19fb0a 100644 --- a/docs/LIBOBS-CHANGES.md +++ b/docs/LIBOBS-CHANGES.md @@ -28,7 +28,7 @@ missing files to recreate in `patches/`: | [7e7a5a383](https://github.com/ZabLaboratory/obs-studio/commit/7e7a5a383) | Adds a separate Program-return camera. | | [b33f831b8](https://github.com/ZabLaboratory/obs-studio/commit/b33f831b8) | Resolves embedded modules and registers Program return. | -On top of this pin, 3.0.0 carries **52 patch files**: 51 root-OBS patches +On top of this pin, 3.0.0 carries **54 patch files**: 53 root-OBS patches and one nested obs-browser patch. [build-win.ps1](../scripts/build-win.ps1) sorts complete filenames lexically, routes names containing `obs-browser` to `upstream/plugins/obs-browser/`, and applies the others to `upstream/`. @@ -553,6 +553,34 @@ Further restricts automatic current readback to a physical graphics adapter. A s Affected files: `libobs/obs-video.c`. +### 0060-fix-nvidia-effect-initialization-and-logging + +[Source patch](../patches/0060-fix-nvidia-effect-initialization-and-logging.patch) + +Defers initial/recreated blur loading until source and destination images are +bound. Live intensity updates retain reload and report failed SDK statuses. +Null/empty logger messages are ignored; real SDK diagnostics are preserved. +The installed-SDK probe exercises live intensity, exact video bypass, stacked +filters, 20 native scene changes and native audio meters. A passing load or +readback does not qualify audio intensity: the probe rejects the reproduced +AFX 1.6.1.2 zero-intensity failure. + +Affected files: `plugins/nv-filters/nvidia-audiofx-filter.c`, +`plugins/nv-filters/nvidia-videofx-filter.c`. + +### 0061-fix-nvidia-audio-zero-intensity-bypass + +[Source patch](../patches/0061-fix-nvidia-audio-zero-intensity-bypass.patch) + +Guarantees that intensity zero passes original PCM through the existing 10 ms +FIFO, including bypass of VAD. An atomic flag preserves native enabled state +and avoids the reproduced AFX 1.6.1.2 denoiser near-silence at zero. Nonzero SDK +processing remains unchanged. No reset/reload, allocation or additional audio +thread lock is introduced. The regression checks all three methods against a +contemporaneous disabled-filter control and reapplies intensity one. + +Affected file: `plugins/nv-filters/nvidia-audiofx-filter.c`. + ## Validation boundaries and retained studies The native CPU readback study records 100 warm-up and 100 measured Cuts per diff --git a/patches/0060-fix-nvidia-effect-initialization-and-logging.patch b/patches/0060-fix-nvidia-effect-initialization-and-logging.patch new file mode 100644 index 00000000..cad1a157 --- /dev/null +++ b/patches/0060-fix-nvidia-effect-initialization-and-logging.patch @@ -0,0 +1,82 @@ +From 48f3a5b57f19d5145773259e43238a4f815d86c0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?ClodoCap=C3=A9o?= + <159788250+ClodoCapeo@users.noreply.github.com> +Date: Sat, 10 Oct 2026 12:19:28 +0200 +Subject: [PATCH] fix(nv-filters): bind blur images before loading and guard + empty SDK logs + +The installed VFX 0.7.6 SDK reports Missing src dst when blur is loaded +during creation/reset before any input/output image has been bound. +Defer the initial/recreated blur load to init_blur_images, which binds +both images. Keep reloads on live strength changes after allocation and +report failed SetF32/Load statuses instead of discarding them. + +SDK logger teardown can call back with a null/empty message. Do not pass +that to a %s error log; preserve every nonempty SDK diagnostic. + +This requires editing nv-filters' private initialization/reset/logger +callbacks, which a separate Pulsar plugin cannot intercept. It is +eligible for upstream OBS review; secure SDK loader policy is unchanged. +Work unit: Eleven local-20261010-nvidia-sdk-integration, Pulsar PR #301. +This patch does not fix the separately reproduced AFX live intensity +failure and does not reinterpret an SDK load as full qualification. +--- + plugins/nv-filters/nvidia-audiofx-filter.c | 2 ++ + plugins/nv-filters/nvidia-videofx-filter.c | 14 ++++++++++---- + 2 files changed, 12 insertions(+), 4 deletions(-) + +diff --git a/plugins/nv-filters/nvidia-audiofx-filter.c b/plugins/nv-filters/nvidia-audiofx-filter.c +index 4382eb50c..fc0c39141 100644 +--- a/plugins/nv-filters/nvidia-audiofx-filter.c ++++ b/plugins/nv-filters/nvidia-audiofx-filter.c +@@ -653,6 +653,8 @@ static void nvidia_audio_update(void *data, obs_data_t *s) + static void nvafx_logger_callback(void *data, const char *msg) + { + UNUSED_PARAMETER(data); ++ if (!msg || !*msg) ++ return; + blog(LOG_ERROR, "[NVIDIA Audio Effects: Error - '%s'] ", msg); + } + +diff --git a/plugins/nv-filters/nvidia-videofx-filter.c b/plugins/nv-filters/nvidia-videofx-filter.c +index 009083ebb..9e80a59b9 100644 +--- a/plugins/nv-filters/nvidia-videofx-filter.c ++++ b/plugins/nv-filters/nvidia-videofx-filter.c +@@ -146,7 +146,12 @@ static void nvvfx_filter_update(void *data, obs_data_t *settings) + if (filter->strength != strength) { + filter->strength = strength; + vfxErr = NvVFX_SetF32(filter->handle_blur, NVVFX_STRENGTH, filter->strength); +- vfxErr = NvVFX_Load(filter->handle_blur); ++ /* The blur SDK requires bound source/destination images at load. ++ * First render performs that load in init_blur_images(). */ ++ if (NVCV_SUCCESS == vfxErr && filter->images_allocated) ++ vfxErr = NvVFX_Load(filter->handle_blur); ++ if (NVCV_SUCCESS != vfxErr) ++ error("Error updating blur FX %i", vfxErr); + } + } + } +@@ -291,6 +296,8 @@ static bool nvvfx_filter_create_internal(struct nvvfx_data *filter) + static void nvvfx_logger_callback(void *data, const char *msg) + { + UNUSED_PARAMETER(data); ++ if (!msg || !*msg) ++ return; + blog(LOG_ERROR, "[NVIDIA Video Effect: '%s']", msg); + } + +@@ -431,9 +438,8 @@ static void nvvfx_filter_reset(void *data, calldata_t *calldata) + vfxErr = NvVFX_SetF32(filter->handle_blur, NVVFX_STRENGTH, filter->strength); + if (NVCV_SUCCESS != vfxErr) + error("Error loading NVIDIA Video FX %i", vfxErr); +- vfxErr = NvVFX_Load(filter->handle_blur); +- if (NVCV_SUCCESS != vfxErr) +- error("Error loading blur FX %i", vfxErr); ++ /* Newly recreated handles have no bound images yet. The next ++ * init_blur_images() binds them before loading the effect. */ + } + + filter->images_allocated = false; +-- +2.54.0.windows.1 + diff --git a/patches/0061-fix-nvidia-audio-zero-intensity-bypass.patch b/patches/0061-fix-nvidia-audio-zero-intensity-bypass.patch new file mode 100644 index 00000000..1bb13911 --- /dev/null +++ b/patches/0061-fix-nvidia-audio-zero-intensity-bypass.patch @@ -0,0 +1,65 @@ +From e2aca78947507d2c75c85e44aa6ea1db81b3c46d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?ClodoCap=C3=A9o?= + <159788250+ClodoCapeo@users.noreply.github.com> +Date: Sat, 10 Oct 2026 12:27:02 +0200 +Subject: [PATCH] fix(nv-filters): guarantee audio intensity zero passes + original PCM + +The installed AFX 1.6.1.2 denoiser returns near-silence when changing +intensity from one to zero. Disabling the same filter restores the +original signal, proving the input continues running. + +Publish a bypass flag atomically from the existing settings update. +At intensity zero, preserve the original PCM through the existing +10 ms FIFO instead of calling the SDK. Retain the native enabled +state, timestamps, SDK method/VAD controls and all nonzero processing. +No reset, model reload, allocation or new lock is added on the audio +thread. Intensity zero also bypasses VAD as a zero-impact effect. + +This affects nv-filters' private audio pipeline and cannot be supplied +by a separate plugin. Eligible for upstream OBS review. SDK loading +and model validation policy are unchanged. +Work unit: Eleven local-20261010-nvidia-sdk-integration, Pulsar PR #301. +The regression compares zero, disabled and reapplied-one magnitude +for all three methods; intermediate SDK ratios remain separately tested. +--- + plugins/nv-filters/nvidia-audiofx-filter.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/plugins/nv-filters/nvidia-audiofx-filter.c b/plugins/nv-filters/nvidia-audiofx-filter.c +index fc0c39141..85150ed0b 100644 +--- a/plugins/nv-filters/nvidia-audiofx-filter.c ++++ b/plugins/nv-filters/nvidia-audiofx-filter.c +@@ -79,6 +79,7 @@ struct nvidia_audio_data { + * & the filter is destroyed. + */ + bool use_nvafx; ++ volatile bool bypass_nvafx; + + /* this tracks if the SDK is found */ + bool nvidia_sdk_dir_found; +@@ -576,6 +577,11 @@ static void nvidia_audio_update(void *data, obs_data_t *s) + ng->latency = 1000000000LL / (1000 / BUFFER_SIZE_MSEC); + + float intensity = (float)obs_data_get_double(s, S_NVAFX_INTENSITY); ++ /* SDK 1.6.1.2 denoiser does not pass through at live intensity zero. ++ * Use the original PCM through the existing 10 ms FIFO instead. This ++ * is independent of the source's enabled state and needs no model ++ * reload/reset or allocation on the audio thread. */ ++ os_atomic_set_bool(&ng->bypass_nvafx, intensity == 0.0f); + bool vad = obs_data_get_bool(s, S_NVAFX_VAD); + + /*-------------------------------------------------------------------*/ +@@ -833,7 +839,8 @@ static inline void process(struct nvidia_audio_data *ng) + for (size_t i = 0; i < ng->channels; i++) + deque_pop_front(&ng->input_buffers[i], ng->copy_buffers[i], ng->frames * sizeof(float)); + +- if (ng->use_nvafx && nvidia_afx_loaded && ng->nvafx_initialized) { ++ if (ng->use_nvafx && nvidia_afx_loaded && ng->nvafx_initialized && ++ !os_atomic_load_bool(&ng->bypass_nvafx)) { + process_fx(ng); + } + +-- +2.54.0.windows.1 + diff --git a/patches/README.md b/patches/README.md index 94e25119..7e461239 100644 --- a/patches/README.md +++ b/patches/README.md @@ -1,6 +1,6 @@ # Pulsar OBS patch stack -Pulsar 3.0.0 carries **52 patches** above the pinned +Pulsar 3.0.0 carries **54 patches** above the pinned `ZabLaboratory/obs-studio` revision `bd73b922891e56839b0bc86bdc519802802f9d68`. Five foundational changes are already in that fork revision. @@ -55,6 +55,19 @@ Patch 0059 bounds read-owned source profiling to a five-second lease, independen of explicit resource-trace policy. The native `probe-source-telemetry.py` checks warm samples during polling, expiry after idle and explicit component maxima. +Patch 0060 defers initial/recreated NVIDIA blur loading until its source and +destination images are bound. Live updates still reload and report failures. +The AFX/VFX logger callbacks ignore null/empty messages while preserving actual +SDK diagnostics. The installed-SDK probe records DLL hashes, live intensities, +stacked filters and scene-switch counters. It deliberately fails when AFX +intensity zero does not pass the signal through; this patch does not fix that +separate SDK behavior. + +Patch 0061 supplies a zero-impact audio bypass through the existing PCM FIFO +using an atomic settings flag. It retains the filter's enabled state and does +not reset/reload SDK models. Nonzero intensity remains an SDK operation and +must be qualified separately, including intermediate values. + The build records the upstream pin, patch-content fingerprint and applied HEAD. It reuses only an exact clean match; `-RefreshPatches` forces replay. This preserves incremental object caches without accepting a different diff --git a/scripts/probe-nvidia-effects.py b/scripts/probe-nvidia-effects.py index efc537b5..53c1054a 100644 --- a/scripts/probe-nvidia-effects.py +++ b/scripts/probe-nvidia-effects.py @@ -58,11 +58,12 @@ async def exercise(proc, work, report): Image.fromarray(pixels).save(work / "stripes.png") await wire.call("CreateInput", {"sceneName": "NvidiaSDKRegression", "inputName": "Stripes", "inputKind": "image_source", "inputSettings": {"file": str(work / "stripes.png")}, "sceneItemEnabled": True}) await wire.call("SetCurrentProgramScene", {"sceneName": "NvidiaSDKRegression"}) - async def image(): - data = await wire.call("GetSourceScreenshot", {"sourceName": "Stripes", "imageFormat": "png", "imageWidth": 480, "imageHeight": 270}) + async def image(source="Stripes"): + data = await wire.call("GetSourceScreenshot", {"sourceName": source, "imageFormat": "png", "imageWidth": 480, "imageHeight": 270}) return np.asarray(Image.open(io.BytesIO(base64.b64decode(data["imageData"].split(",", 1)[1]))).convert("RGBA")) await asyncio.sleep(1) baseline = await image() + baseline_program = await image("NvidiaSDKRegression") report["video"] = [] for kind in KINDS[:3]: settings = {"mode": 1, "threshold": .8, "processing_interval": 1} if kind == KINDS[0] else {"intensity": .8} @@ -77,13 +78,45 @@ async def image(): samples.append(await wire.call("GetStats")) treated = await image() row = {"kind": kind, "settings": actual["filterSettings"], "pixelDifferenceMean": float(np.abs(treated.astype(float)-baseline).mean()), "alphaRange": [int(treated[:,:,3].min()), int(treated[:,:,3].max())], "frameDelta": samples[-1]["renderTotalFrames"]-before["renderTotalFrames"], "skippedDelta": samples[-1]["renderSkippedFrames"]-before["renderSkippedFrames"], "meanRenderMs": sum(s["averageFrameRenderTime"] for s in samples)/len(samples)} + row["programScenePixelDifferenceMean"] = float(np.abs((await image("NvidiaSDKRegression")).astype(float)-baseline_program).mean()) report["video"].append(row) assert row["pixelDifferenceMean"] > 1, row + assert row["programScenePixelDifferenceMean"] > 1, row assert row["frameDelta"] >= 250 and row["skippedDelta"] == 0, row + if kind != KINDS[0]: + row["liveIntensity"] = [] + for intensity in [0.0, 0.2, 0.8, 0.0, 0.8]: + await wire.call("SetSourceFilterSettings", {"sourceName": "Stripes", "filterName": "sdk/test", "filterSettings": {"intensity": intensity}, "overlay": True}) + await asyncio.sleep(.6) + current = await image() + difference = float(np.abs(current.astype(float)-baseline).mean()) + row["liveIntensity"].append({"intensity": intensity, "pixelDifferenceMean": difference}) + assert difference == 0 if intensity == 0 else difference > 1, row await wire.call("SetSourceFilterEnabled", {"sourceName": "Stripes", "filterName": "sdk/test", "filterEnabled": False}) await asyncio.sleep(.5) assert np.array_equal(await image(), baseline), kind await wire.call("RemoveSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/test"}) + # Reuse one processed source across scene switches; no physical device + # or inactive-scene cache is involved in this isolated native check. + await wire.call("CreateScene", {"sceneName": "NvidiaSDKRegressionB"}) + await wire.call("CreateSceneItem", {"sceneName": "NvidiaSDKRegressionB", "sourceName": "Stripes", "sceneItemEnabled": True}) + for kind in KINDS[1:3]: + await wire.call("CreateSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/"+kind, "filterKind": kind, "filterSettings": {"intensity": .4}}) + await asyncio.sleep(2) + before = await wire.call("GetStats") + report["stackedSceneSwitches"] = [] + for index in range(20): + scene = "NvidiaSDKRegressionB" if index % 2 == 0 else "NvidiaSDKRegression" + await wire.call("SetCurrentProgramScene", {"sceneName": scene}) + await asyncio.sleep(.2) + assert (await wire.call("GetCurrentProgramScene"))["currentProgramSceneName"] == scene + report["stackedSceneSwitches"].append({"scene": scene, "pixelDifferenceMean": float(np.abs((await image()).astype(float)-baseline).mean())}) + assert report["stackedSceneSwitches"][-1]["pixelDifferenceMean"] > 1 + after = await wire.call("GetStats") + report["stackedStats"] = {"frameDelta": after["renderTotalFrames"]-before["renderTotalFrames"], "skippedDelta": after["renderSkippedFrames"]-before["renderSkippedFrames"], "lastRenderMs": after["averageFrameRenderTime"]} + assert report["stackedStats"]["frameDelta"] >= 240 and report["stackedStats"]["skippedDelta"] == 0 + for kind in KINDS[1:3]: + await wire.call("RemoveSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/"+kind}) rng = np.random.default_rng(20261010) noise = (rng.normal(0, .08, 48000*12).clip(-1, 1)*32767).astype(" intermediate[0]["magnitude"]*1.2 + report["audio"]["methods"][-1]["intermediateIntensityPassed"] = intermediate_passed + if not intermediate_passed: + live_intensity_failures.append(method+"/intermediate") + await wire.call("SetSourceFilterSettings", {"sourceName": "Noise", "filterName": "sdk/audio", "filterSettings": {"intensity": 0.0}, "overlay": True}) + await asyncio.sleep(1) + bypass_magnitude = await levels(2) + report["audio"]["methods"][-1]["liveIntensityZeroMagnitude"] = bypass_magnitude + # A contemporaneous disabled-filter control distinguishes an SDK + # slider failure from a stopped fixture or source-loop gap. + await wire.call("SetSourceFilterEnabled", {"sourceName": "Noise", "filterName": "sdk/audio", "filterEnabled": False}) + await asyncio.sleep(.5) + disabled = await levels(2) + report["audio"]["methods"][-1]["disabledMagnitude"] = disabled + assert disabled > original*.8, ("fixture no longer running", original, disabled) + await wire.call("SetSourceFilterSettings", {"sourceName": "Noise", "filterName": "sdk/audio", "filterSettings": {"intensity": 1.0}, "overlay": True}) + await wire.call("SetSourceFilterEnabled", {"sourceName": "Noise", "filterName": "sdk/audio", "filterEnabled": True}) + await asyncio.sleep(1) + reapplied = await levels(2) + report["audio"]["methods"][-1]["liveIntensityOneMagnitude"] = reapplied + if method in ("denoiser", "dereverb_denoiser"): + assert reapplied < original*.5, (method, original, reapplied) + intensity_passed = bypass_magnitude > disabled*.8 + report["audio"]["methods"][-1]["liveIntensityPassed"] = intensity_passed + if not intensity_passed: + live_intensity_failures.append(method) await wire.call("RemoveSourceFilter", {"sourceName": "Noise", "filterName": "sdk/audio"}) await wire.call("RemoveInput", {"inputName": "Noise"}) assert (await wire.call("GetSourceFilterList", {"sourceName": "Stripes"}))["filters"] == [] - report["checks"] = ["SDK usable/version", "four filters registered", "three VFX pixel changes and exact bypass", "native render timings", "three AFX model methods", "noise suppression signal", "filter removal"] + report["checks"] = ["SDK usable/version", "four filters registered", "three VFX source and Program scene pixel changes and exact bypass", "live VFX intensity", "20 scene switches with stacked VFX", "native render timings", "three AFX model methods", "noise suppression signal", "filter removal"] + report["audio"]["liveIntensityPassed"] = not live_intensity_failures + assert not live_intensity_failures, ("AFX live intensity contract failed", live_intensity_failures) def main(): parser = argparse.ArgumentParser(description=__doc__) @@ -129,7 +197,8 @@ def main(): os.environ["PULSAR_LEGACY_ALIAS"] = "disabled" os.environ["PULSAR_DIRECTSHOW_LEGACY_ALIAS"] = "0" proc = transport.process.PulsarProcess(args.exe.resolve(), "nvenc", work) - report = {"started": time.time(), "mode": "isolated installed NVIDIA SDK synthetic regression", "exe": str(args.exe.resolve()), "exeSha256": hashlib.sha256(args.exe.read_bytes()).hexdigest(), "sdkPaths": {"afx": str(args.afx.resolve()), "vfx": str(args.vfx.resolve())}} + nv_module = args.exe.resolve().parents[2] / "obs-plugins/64bit/nv-filters.dll" + report = {"started": time.time(), "mode": "isolated installed NVIDIA SDK synthetic regression", "exe": str(args.exe.resolve()), "exeSha256": hashlib.sha256(args.exe.read_bytes()).hexdigest(), "nvFiltersSha256": hashlib.sha256(nv_module.read_bytes()).hexdigest(), "sdkPaths": {"afx": str(args.afx.resolve()), "vfx": str(args.vfx.resolve())}} try: asyncio.run(exercise(proc, work, report)) except Exception as error: @@ -140,8 +209,8 @@ def main(): # Store SDK diagnostics, never authentication/stream keys from raw logs. report["sdkLogs"] = [transport.process.SENSITIVE_LOG_VALUE_RE.sub(r"\1\2", line) for line in proc.snapshot() if "NVIDIA" in line or "nvvfx" in line or "nvafx" in line] report["sdkDiagnostics"] = [line for line in report["sdkLogs"] if " ERROR " in line or " WARN " in line] - report["qualification"] = "synthetic functional/performance regression; SDK diagnostics require separate review" - report["passed"] = "failure" not in report and report["shutdown"] == {"exitCode": 0, "forced": False} + report["qualification"] = "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity" + report["passed"] = "failure" not in report and not report["sdkDiagnostics"] and report["shutdown"] == {"exitCode": 0, "forced": False} args.report.parent.mkdir(parents=True, exist_ok=True) args.report.write_text(json.dumps(report, indent=2)+"\n", encoding="utf8", newline="\n") print(json.dumps({key: value for key, value in report.items() if key not in ["capabilities", "sdkLogs"]})) From c5011056f307424d5ce033972b7e07b0b07c45f5 Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Sat, 10 Oct 2026 12:35:21 +0200 Subject: [PATCH 12/17] test: preserve NVIDIA intensity regression and native qualification evidence --- README.md | 4 +- docs/LIBOBS-CHANGES.md | 28 +- .../20261010T103031Z-audio-baseline.json | 602 +++++++++++++ .../eleven/20261010T103031Z-module-build.txt | 21 + .../eleven/20261010T103031Z-native.json | 847 ++++++++++++++++++ .../eleven/20261010T103031Z-patch-replay.json | 6 + .../20261010T103031Z-rejected-reset.json | 604 +++++++++++++ .../eleven/20261010T103031Z-report.md | 94 ++ patches/README.md | 2 +- 9 files changed, 2204 insertions(+), 4 deletions(-) create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-audio-baseline.json create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-module-build.txt create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-native.json create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-patch-replay.json create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-rejected-reset.json create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-report.md diff --git a/README.md b/README.md index e8fa1690..5ffc95fc 100644 --- a/README.md +++ b/README.md @@ -473,7 +473,7 @@ does not recover scene identity from arbitrary pixels. Pulsar is a maintained native fork, not only a JavaScript wrapper around stock OBS. Its OBS submodule is pinned to `bd73b922891e56839b0bc86bdc519802802f9d68`, an OBS 32.1.2-derived revision. -Five foundational modifications are incorporated in that pin; **54 additional +Five foundational modifications are incorporated in that pin; **58 additional patch files** build the current media core. | Area | What Pulsar changes | @@ -598,7 +598,7 @@ as a troubleshooting shortcut. | [Client](packages/pulsar-client/README.md) | TypeScript API, typed events, errors and examples. | | [Embedding](docs/PRISM-EMBEDDING.md) | Host lifecycle, readiness and verified bundle integration. | | [Architecture](docs/ARCHITECTURE.md) | Runtime structure and ownership boundaries. | -| [All libobs changes](docs/LIBOBS-CHANGES.md) | Integrated OBS changes and all 54 source patches. | +| [All libobs changes](docs/LIBOBS-CHANGES.md) | Integrated OBS changes and all 58 source patches. | | [All documentation](docs/README.md) | Current guides, operational runbooks, contracts and historical studies. | | [Development](docs/DEVELOPMENT.md) | Build toolchain and local development. | | [Dual-lane canary](docs/runbooks/pulsar-dual-lane-canary.md) | Qualification and operational checks. | diff --git a/docs/LIBOBS-CHANGES.md b/docs/LIBOBS-CHANGES.md index 9b19fb0a..ca6fc8f2 100644 --- a/docs/LIBOBS-CHANGES.md +++ b/docs/LIBOBS-CHANGES.md @@ -28,7 +28,7 @@ missing files to recreate in `patches/`: | [7e7a5a383](https://github.com/ZabLaboratory/obs-studio/commit/7e7a5a383) | Adds a separate Program-return camera. | | [b33f831b8](https://github.com/ZabLaboratory/obs-studio/commit/b33f831b8) | Resolves embedded modules and registers Program return. | -On top of this pin, 3.0.0 carries **54 patch files**: 53 root-OBS patches +On top of this pin, 3.0.0 carries **58 patch files**: 57 root-OBS patches and one nested obs-browser patch. [build-win.ps1](../scripts/build-win.ps1) sorts complete filenames lexically, routes names containing `obs-browser` to `upstream/plugins/obs-browser/`, and applies the others to `upstream/`. @@ -553,6 +553,32 @@ Further restricts automatic current readback to a physical graphics adapter. A s Affected files: `libobs/obs-video.c`. +### 0057-feat-source-core-memory-readback + +[Source patch](../patches/0057-feat-source-core-memory-readback.patch) + +Adds core-owned source memory statistics consumed by the native GetSourceStats +request. Windows reads allocations and cached frames under the source async +mutex; other platforms return an unavailable sentinel. This lower bound excludes +plugin, driver and GPU allocations. Affected files: `libobs/obs-source.c`, +`libobs/obs.h`. + +### 0058-fix-build-provision-cef-for-full-pulsar + +[Source patch](../patches/0058-fix-build-provision-cef-for-full-pulsar.patch) + +Allows PULSAR_REQUIRE_CEF to request the pinned, hash-verified CEF dependency +independently of the upstream browser target. Full builds opt in; light builds +do not. Affected file: `cmake/common/buildspec_common.cmake`. + +### 0059-fix-source-telemetry-profiling-lease + +[Source patch](../patches/0059-fix-source-telemetry-profiling-lease.patch) + +Bounds read-owned source profiling to a five-second lease independently of +explicit resource tracing. Native telemetry covers polling and idle expiry. +Affected files: `libobs/util/source-profiler.c`, `libobs/util/source-profiler.h`. + ### 0060-fix-nvidia-effect-initialization-and-logging [Source patch](../patches/0060-fix-nvidia-effect-initialization-and-logging.patch) diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-audio-baseline.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-audio-baseline.json new file mode 100644 index 00000000..647e140d --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-audio-baseline.json @@ -0,0 +1,602 @@ +{ + "started": 1791627407.6458888, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "capabilities": { + "active_encoder": "nvenc", + "audio_bitrate": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ], + "capabilities": { + "active_encoder": { + "applicability": "boot-fixed", + "value": "nvenc" + }, + "audio_bitrate": { + "applicability": "live", + "max": 512, + "min": 64, + "step": 32, + "values": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ] + }, + "audio_monitoring": { + "applicability": "live", + "available": true, + "device_bound": true, + "device_id": "{0.0.0.00000000}.{3a9b3ef5-04ae-4b95-88d7-53e24294bc68}", + "device_name": "Default", + "device_selectable": true + }, + "audio_sample_rate": { + "applicability": "read-only", + "hz": 48000 + }, + "audio_speaker_layout": { + "applicability": "read-only", + "channels": 2, + "layout": "stereo" + }, + "audio_tracks": { + "applicability": "read-only", + "bound": 1, + "count": 6, + "tracks": [ + { + "value": 1 + } + ] + }, + "destination_kinds": { + "applicability": "live", + "values": [ + { + "value": "rtmp_custom" + }, + { + "value": "vod_local" + }, + { + "value": "twitch" + }, + { + "value": "youtube" + } + ] + }, + "encoder_families": { + "applicability": "boot-fixed", + "values": [ + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 20, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "ultrafast" + }, + { + "value": "superfast" + }, + { + "value": "veryfast" + }, + { + "value": "faster" + }, + { + "value": "fast" + }, + { + "value": "medium" + }, + { + "value": "slow" + }, + { + "value": "slower" + }, + { + "value": "veryslow" + }, + { + "value": "placebo" + } + ], + "profiles": [ + { + "value": "baseline" + }, + { + "value": "main" + }, + { + "value": "high" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "VBR" + } + ], + "value": "x264" + }, + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 10, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "p1" + }, + { + "value": "p2" + }, + { + "value": "p3" + }, + { + "value": "p4" + }, + { + "value": "p5" + }, + { + "value": "p6" + }, + { + "value": "p7" + } + ], + "profiles": [ + { + "value": "high" + }, + { + "value": "main" + }, + { + "value": "baseline" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "CQP" + }, + { + "value": "VBR" + } + ], + "value": "nvenc" + } + ] + }, + "encoders": { + "applicability": "boot-fixed", + "values": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ] + }, + "filters": { + "applicability": "live", + "values": [ + { + "value": "nvidia_audiofx_filter" + }, + { + "value": "nv_greenscreen_filter" + }, + { + "value": "nv_blur_filter" + }, + { + "value": "nv_background_blur_filter" + }, + { + "value": "mask_filter" + }, + { + "value": "mask_filter_v2" + }, + { + "value": "crop_filter" + }, + { + "value": "gain_filter" + }, + { + "value": "basic_eq_filter" + }, + { + "value": "hdr_tonemap_filter" + }, + { + "value": "color_filter" + }, + { + "value": "color_filter_v2" + }, + { + "value": "scale_filter" + }, + { + "value": "scroll_filter" + }, + { + "value": "gpu_delay" + }, + { + "value": "color_key_filter" + }, + { + "value": "color_key_filter_v2" + }, + { + "value": "clut_filter" + }, + { + "value": "sharpness_filter" + }, + { + "value": "sharpness_filter_v2" + }, + { + "value": "chroma_key_filter" + }, + { + "value": "chroma_key_filter_v2" + }, + { + "value": "async_delay_filter" + }, + { + "value": "noise_suppress_filter" + }, + { + "value": "noise_suppress_filter_v2" + }, + { + "value": "invert_polarity_filter" + }, + { + "value": "noise_gate_filter" + }, + { + "value": "compressor_filter" + }, + { + "value": "limiter_filter" + }, + { + "value": "expander_filter" + }, + { + "value": "upward_compressor_filter" + }, + { + "value": "luma_key_filter" + }, + { + "value": "luma_key_filter_v2" + }, + { + "value": "pulsar_vignette_filter" + } + ] + }, + "graphics_adapters": { + "active_index": 0, + "applicability": "read-only", + "values": [ + { + "index": 0, + "value": "NVIDIA GeForce RTX 3070" + } + ] + }, + "nv_filters": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "module_loaded": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "output_scales": { + "applicability": "boot-fixed", + "canvas": { + "height": 1080, + "width": 1920 + }, + "values": [ + { + "height": 1080, + "scale": 1.0, + "value": "1920x1080", + "width": 1920 + } + ] + }, + "record_container": { + "applicability": "boot-fixed", + "value": "mp4", + "values": [ + { + "value": "mp4" + }, + { + "value": "mkv" + } + ] + }, + "record_markers": { + "add_chapter": false, + "applicability": "read-only", + "split_file": true + }, + "source_kinds": { + "applicability": "live", + "values": [ + { + "value": "audio_line" + }, + { + "value": "pulsar_preview_frame" + }, + { + "value": "image_source" + }, + { + "value": "color_source" + }, + { + "value": "color_source_v2" + }, + { + "value": "color_source_v3" + }, + { + "value": "slideshow" + }, + { + "value": "slideshow_v2" + }, + { + "value": "ffmpeg_source" + }, + { + "value": "text_gdiplus" + }, + { + "value": "text_gdiplus_v2" + }, + { + "value": "text_gdiplus_v3" + }, + { + "value": "browser_source" + }, + { + "value": "text_ft2_source" + }, + { + "value": "text_ft2_source_v2" + }, + { + "value": "vlc_source" + }, + { + "value": "monitor_capture" + }, + { + "value": "window_capture" + }, + { + "value": "game_capture" + }, + { + "value": "dshow_input" + }, + { + "value": "wasapi_input_capture" + }, + { + "value": "wasapi_output_capture" + }, + { + "value": "wasapi_process_output_capture" + } + ] + }, + "video_bitrate": { + "applicability": "live", + "max": 50000, + "min": 200, + "step": 50 + }, + "video_colorimetry": { + "applicability": "read-only", + "format": "NV12", + "range": "Partial", + "value": "709" + } + }, + "encoders": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ], + "version": 1, + "video_bitrate": { + "max": 50000, + "min": 200 + } + }, + "audio": { + "fixture": "seeded white noise, raw native volume-meter magnitude", + "baseline": 0.0797431181217062, + "methods": [ + { + "method": "denoiser", + "magnitude": 0.008092059618577058, + "liveIntensityZeroMagnitude": 1.4734066891220674e-06, + "disabledMagnitude": 0.07975319524606068, + "liveIntensityOneMagnitude": 0.005154104241983119 + } + ] + }, + "failure": { + "type": "AssertionError", + "message": "('denoiser', 'intensity zero is not passthrough', 0.07975319524606068, 1.4734066891220674e-06)" + }, + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T10:16:47Z INFO lvtC6QiuHOOCBhBy libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T10:16:47Z INFO lvtC6QiuHOOCBhBy libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T10:16:48Z INFO lvtC6QiuHOOCBhBy NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T10:16:48Z INFO lvtC6QiuHOOCBhBy NVIDIA Audio Effects: | enabled", + "2026-10-10T10:16:48Z INFO lvtC6QiuHOOCBhBy NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T10:16:48Z INFO lvtC6QiuHOOCBhBy NVIDIA VIDEO FX | : enabled, redistributable found", + "2026-10-10T10:16:52Z INFO lvtC6QiuHOOCBhBy NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK" + ], + "sdkDiagnostics": [], + "qualification": "synthetic functional/performance regression; SDK diagnostics require separate review", + "passed": false +} diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-module-build.txt b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-module-build.txt new file mode 100644 index 00000000..a068637f --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-module-build.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.28) +project(nvidia_lifecycle_probe LANGUAGES C) +set(PULSAR_ROOT "D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261009-capability-audit") +set(OBS_SOURCE "${PULSAR_ROOT}/.cache/nvidia-patch-source") +set(OBS_BUILD "${PULSAR_ROOT}/.cache/upstream-build") +add_library(nv-filters MODULE + "${OBS_SOURCE}/plugins/nv-filters/nv-filters.c" + "${OBS_SOURCE}/plugins/nv-filters/nvidia-audiofx-filter.c" + "${OBS_SOURCE}/plugins/nv-filters/nvidia-videofx-filter.c") +target_compile_definitions(nv-filters PRIVATE LIBNVAFX_ENABLED LIBNVVFX_ENABLED) +target_include_directories(nv-filters PRIVATE + "${OBS_SOURCE}/libobs" + "${OBS_SOURCE}/deps/w32-pthreads" + "${OBS_BUILD}/config" + "D:/Documents/Zab/Pulsar/upstream/build_x64/libobs" + "${PULSAR_ROOT}/plugins/pulsar-nv-secure-load") +target_link_libraries(nv-filters PRIVATE + "${OBS_BUILD}/libobs/RelWithDebInfo/obs.lib" + "D:/Documents/Zab/Pulsar/upstream/build_x64/deps/w32-pthreads/RelWithDebInfo/w32-pthreads.lib" + version shell32 ole32) +set_target_properties(nv-filters PROPERTIES PREFIX "") diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-native.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-native.json new file mode 100644 index 00000000..caea0f76 --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-native.json @@ -0,0 +1,847 @@ +{ + "started": 1791627927.6948059, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "nvFiltersSha256": "59df7e4992d186d83dc5ad8415148b5533111c923336d9ca9dfd32ef92441c3b", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "capabilities": { + "active_encoder": "nvenc", + "audio_bitrate": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ], + "capabilities": { + "active_encoder": { + "applicability": "boot-fixed", + "value": "nvenc" + }, + "audio_bitrate": { + "applicability": "live", + "max": 512, + "min": 64, + "step": 32, + "values": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ] + }, + "audio_monitoring": { + "applicability": "live", + "available": true, + "device_bound": true, + "device_id": "{0.0.0.00000000}.{3a9b3ef5-04ae-4b95-88d7-53e24294bc68}", + "device_name": "Default", + "device_selectable": true + }, + "audio_sample_rate": { + "applicability": "read-only", + "hz": 48000 + }, + "audio_speaker_layout": { + "applicability": "read-only", + "channels": 2, + "layout": "stereo" + }, + "audio_tracks": { + "applicability": "read-only", + "bound": 1, + "count": 6, + "tracks": [ + { + "value": 1 + } + ] + }, + "destination_kinds": { + "applicability": "live", + "values": [ + { + "value": "rtmp_custom" + }, + { + "value": "vod_local" + }, + { + "value": "twitch" + }, + { + "value": "youtube" + } + ] + }, + "encoder_families": { + "applicability": "boot-fixed", + "values": [ + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 20, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "ultrafast" + }, + { + "value": "superfast" + }, + { + "value": "veryfast" + }, + { + "value": "faster" + }, + { + "value": "fast" + }, + { + "value": "medium" + }, + { + "value": "slow" + }, + { + "value": "slower" + }, + { + "value": "veryslow" + }, + { + "value": "placebo" + } + ], + "profiles": [ + { + "value": "baseline" + }, + { + "value": "main" + }, + { + "value": "high" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "VBR" + } + ], + "value": "x264" + }, + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 10, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "p1" + }, + { + "value": "p2" + }, + { + "value": "p3" + }, + { + "value": "p4" + }, + { + "value": "p5" + }, + { + "value": "p6" + }, + { + "value": "p7" + } + ], + "profiles": [ + { + "value": "high" + }, + { + "value": "main" + }, + { + "value": "baseline" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "CQP" + }, + { + "value": "VBR" + } + ], + "value": "nvenc" + } + ] + }, + "encoders": { + "applicability": "boot-fixed", + "values": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ] + }, + "filters": { + "applicability": "live", + "values": [ + { + "value": "nvidia_audiofx_filter" + }, + { + "value": "nv_greenscreen_filter" + }, + { + "value": "nv_blur_filter" + }, + { + "value": "nv_background_blur_filter" + }, + { + "value": "mask_filter" + }, + { + "value": "mask_filter_v2" + }, + { + "value": "crop_filter" + }, + { + "value": "gain_filter" + }, + { + "value": "basic_eq_filter" + }, + { + "value": "hdr_tonemap_filter" + }, + { + "value": "color_filter" + }, + { + "value": "color_filter_v2" + }, + { + "value": "scale_filter" + }, + { + "value": "scroll_filter" + }, + { + "value": "gpu_delay" + }, + { + "value": "color_key_filter" + }, + { + "value": "color_key_filter_v2" + }, + { + "value": "clut_filter" + }, + { + "value": "sharpness_filter" + }, + { + "value": "sharpness_filter_v2" + }, + { + "value": "chroma_key_filter" + }, + { + "value": "chroma_key_filter_v2" + }, + { + "value": "async_delay_filter" + }, + { + "value": "noise_suppress_filter" + }, + { + "value": "noise_suppress_filter_v2" + }, + { + "value": "invert_polarity_filter" + }, + { + "value": "noise_gate_filter" + }, + { + "value": "compressor_filter" + }, + { + "value": "limiter_filter" + }, + { + "value": "expander_filter" + }, + { + "value": "upward_compressor_filter" + }, + { + "value": "luma_key_filter" + }, + { + "value": "luma_key_filter_v2" + }, + { + "value": "pulsar_vignette_filter" + } + ] + }, + "graphics_adapters": { + "active_index": 0, + "applicability": "read-only", + "values": [ + { + "index": 0, + "value": "NVIDIA GeForce RTX 3070" + } + ] + }, + "nv_filters": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "module_loaded": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "output_scales": { + "applicability": "boot-fixed", + "canvas": { + "height": 1080, + "width": 1920 + }, + "values": [ + { + "height": 1080, + "scale": 1.0, + "value": "1920x1080", + "width": 1920 + } + ] + }, + "record_container": { + "applicability": "boot-fixed", + "value": "mp4", + "values": [ + { + "value": "mp4" + }, + { + "value": "mkv" + } + ] + }, + "record_markers": { + "add_chapter": false, + "applicability": "read-only", + "split_file": true + }, + "source_kinds": { + "applicability": "live", + "values": [ + { + "value": "audio_line" + }, + { + "value": "pulsar_preview_frame" + }, + { + "value": "image_source" + }, + { + "value": "color_source" + }, + { + "value": "color_source_v2" + }, + { + "value": "color_source_v3" + }, + { + "value": "slideshow" + }, + { + "value": "slideshow_v2" + }, + { + "value": "ffmpeg_source" + }, + { + "value": "text_gdiplus" + }, + { + "value": "text_gdiplus_v2" + }, + { + "value": "text_gdiplus_v3" + }, + { + "value": "browser_source" + }, + { + "value": "text_ft2_source" + }, + { + "value": "text_ft2_source_v2" + }, + { + "value": "vlc_source" + }, + { + "value": "monitor_capture" + }, + { + "value": "window_capture" + }, + { + "value": "game_capture" + }, + { + "value": "dshow_input" + }, + { + "value": "wasapi_input_capture" + }, + { + "value": "wasapi_output_capture" + }, + { + "value": "wasapi_process_output_capture" + } + ] + }, + "video_bitrate": { + "applicability": "live", + "max": 50000, + "min": 200, + "step": 50 + }, + "video_colorimetry": { + "applicability": "read-only", + "format": "NV12", + "range": "Partial", + "value": "709" + } + }, + "encoders": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ], + "version": 1, + "video_bitrate": { + "max": 50000, + "min": 200 + } + }, + "video": [ + { + "kind": "nv_greenscreen_filter", + "settings": { + "mode": 1, + "processing_interval": 1, + "threshold": 0.8 + }, + "pixelDifferenceMean": 137.625, + "alphaRange": [ + 0, + 0 + ], + "frameDelta": 302, + "skippedDelta": 0, + "meanRenderMs": 3.9544656, + "programScenePixelDifferenceMean": 137.625 + }, + { + "kind": "nv_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 302, + "skippedDelta": 0, + "meanRenderMs": 1.7056802000000002, + "programScenePixelDifferenceMean": 58.8734375, + "liveIntensity": [ + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.2, + "pixelDifferenceMean": 58.86875 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + }, + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + } + ] + }, + { + "kind": "nv_background_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 303, + "skippedDelta": 0, + "meanRenderMs": 5.0464910000000005, + "programScenePixelDifferenceMean": 58.8734375, + "liveIntensity": [ + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.2, + "pixelDifferenceMean": 58.86875 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + }, + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + } + ] + } + ], + "stackedSceneSwitches": [ + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + } + ], + "stackedStats": { + "frameDelta": 243, + "skippedDelta": 0, + "lastRenderMs": 5.834009 + }, + "audio": { + "fixture": "seeded white noise, raw native volume-meter magnitude", + "baseline": 0.07978200539946556, + "methods": [ + { + "method": "denoiser", + "magnitude": 0.004041469331623932, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 2.8712613259601438e-06 + }, + { + "intensity": 0.25, + "magnitude": 1.4277631998795437e-06 + } + ], + "intermediateIntensityPassed": false, + "liveIntensityZeroMagnitude": 0.07655380286404755, + "disabledMagnitude": 0.07982581467009507, + "liveIntensityOneMagnitude": 0.00511255815952912, + "liveIntensityPassed": true + }, + { + "method": "dereverb", + "magnitude": 0.02801784102786144, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.02073710935280448 + }, + { + "intensity": 0.25, + "magnitude": 0.059435459214352795 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.07923362904884776, + "disabledMagnitude": 0.07996785139235166, + "liveIntensityOneMagnitude": 0.008756139909382909, + "liveIntensityPassed": true + }, + { + "method": "dereverb_denoiser", + "magnitude": 0.004756835687226355, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.01923845746459998 + }, + { + "intensity": 0.25, + "magnitude": 0.0584067548230543 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.07933090152519365, + "disabledMagnitude": 0.08012001058803155, + "liveIntensityOneMagnitude": 0.004474495904754081, + "liveIntensityPassed": true + } + ], + "liveIntensityPassed": false + }, + "checks": [ + "SDK usable/version", + "four filters registered", + "three VFX source and Program scene pixel changes and exact bypass", + "live VFX intensity", + "20 scene switches with stacked VFX", + "native render timings", + "three AFX model methods", + "noise suppression signal", + "filter removal" + ], + "failure": { + "type": "AssertionError", + "message": "('AFX intensity zero is not passthrough', ['denoiser/intermediate'])" + }, + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T10:25:27Z INFO s7yk-BF-cTeREx9A libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T10:25:27Z INFO s7yk-BF-cTeREx9A libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T10:25:28Z INFO s7yk-BF-cTeREx9A NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T10:25:28Z INFO s7yk-BF-cTeREx9A NVIDIA Audio Effects: | enabled", + "2026-10-10T10:25:28Z INFO s7yk-BF-cTeREx9A NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T10:25:28Z INFO s7yk-BF-cTeREx9A NVIDIA VIDEO FX | : enabled, redistributable found", + "2026-10-10T10:26:11Z INFO s7yk-BF-cTeREx9A NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T10:26:31Z INFO s7yk-BF-cTeREx9A NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T10:26:51Z INFO s7yk-BF-cTeREx9A NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK" + ], + "sdkDiagnostics": [], + "qualification": "synthetic functional/performance regression; SDK diagnostics require separate review", + "passed": false +} diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-patch-replay.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-patch-replay.json new file mode 100644 index 00000000..edfb650f --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-patch-replay.json @@ -0,0 +1,6 @@ +{ + "passed": true, + "pin": "bd73b922891e56839b0bc86bdc519802802f9d68", + "candidate": "e2aca78947507d2c75c85e44aa6ea1db81b3c46d", + "check": "Both patches replay in order on pinned native sources and match the compiled candidate. Earlier patch stack has no nv-filters changes." +} diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-rejected-reset.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-rejected-reset.json new file mode 100644 index 00000000..5d4b4d73 --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-rejected-reset.json @@ -0,0 +1,604 @@ +{ + "started": 1791627761.6010122, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "nvFiltersSha256": "6b238cf9aa0abe9176503c592bf4bf471388678f5a6e2be34f92daaf2a4fa444", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "capabilities": { + "active_encoder": "nvenc", + "audio_bitrate": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ], + "capabilities": { + "active_encoder": { + "applicability": "boot-fixed", + "value": "nvenc" + }, + "audio_bitrate": { + "applicability": "live", + "max": 512, + "min": 64, + "step": 32, + "values": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ] + }, + "audio_monitoring": { + "applicability": "live", + "available": true, + "device_bound": true, + "device_id": "{0.0.0.00000000}.{3a9b3ef5-04ae-4b95-88d7-53e24294bc68}", + "device_name": "Default", + "device_selectable": true + }, + "audio_sample_rate": { + "applicability": "read-only", + "hz": 48000 + }, + "audio_speaker_layout": { + "applicability": "read-only", + "channels": 2, + "layout": "stereo" + }, + "audio_tracks": { + "applicability": "read-only", + "bound": 1, + "count": 6, + "tracks": [ + { + "value": 1 + } + ] + }, + "destination_kinds": { + "applicability": "live", + "values": [ + { + "value": "rtmp_custom" + }, + { + "value": "vod_local" + }, + { + "value": "twitch" + }, + { + "value": "youtube" + } + ] + }, + "encoder_families": { + "applicability": "boot-fixed", + "values": [ + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 20, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "ultrafast" + }, + { + "value": "superfast" + }, + { + "value": "veryfast" + }, + { + "value": "faster" + }, + { + "value": "fast" + }, + { + "value": "medium" + }, + { + "value": "slow" + }, + { + "value": "slower" + }, + { + "value": "veryslow" + }, + { + "value": "placebo" + } + ], + "profiles": [ + { + "value": "baseline" + }, + { + "value": "main" + }, + { + "value": "high" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "VBR" + } + ], + "value": "x264" + }, + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 10, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "p1" + }, + { + "value": "p2" + }, + { + "value": "p3" + }, + { + "value": "p4" + }, + { + "value": "p5" + }, + { + "value": "p6" + }, + { + "value": "p7" + } + ], + "profiles": [ + { + "value": "high" + }, + { + "value": "main" + }, + { + "value": "baseline" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "CQP" + }, + { + "value": "VBR" + } + ], + "value": "nvenc" + } + ] + }, + "encoders": { + "applicability": "boot-fixed", + "values": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ] + }, + "filters": { + "applicability": "live", + "values": [ + { + "value": "nvidia_audiofx_filter" + }, + { + "value": "nv_greenscreen_filter" + }, + { + "value": "nv_blur_filter" + }, + { + "value": "nv_background_blur_filter" + }, + { + "value": "mask_filter" + }, + { + "value": "mask_filter_v2" + }, + { + "value": "crop_filter" + }, + { + "value": "gain_filter" + }, + { + "value": "basic_eq_filter" + }, + { + "value": "hdr_tonemap_filter" + }, + { + "value": "color_filter" + }, + { + "value": "color_filter_v2" + }, + { + "value": "scale_filter" + }, + { + "value": "scroll_filter" + }, + { + "value": "gpu_delay" + }, + { + "value": "color_key_filter" + }, + { + "value": "color_key_filter_v2" + }, + { + "value": "clut_filter" + }, + { + "value": "sharpness_filter" + }, + { + "value": "sharpness_filter_v2" + }, + { + "value": "chroma_key_filter" + }, + { + "value": "chroma_key_filter_v2" + }, + { + "value": "async_delay_filter" + }, + { + "value": "noise_suppress_filter" + }, + { + "value": "noise_suppress_filter_v2" + }, + { + "value": "invert_polarity_filter" + }, + { + "value": "noise_gate_filter" + }, + { + "value": "compressor_filter" + }, + { + "value": "limiter_filter" + }, + { + "value": "expander_filter" + }, + { + "value": "upward_compressor_filter" + }, + { + "value": "luma_key_filter" + }, + { + "value": "luma_key_filter_v2" + }, + { + "value": "pulsar_vignette_filter" + } + ] + }, + "graphics_adapters": { + "active_index": 0, + "applicability": "read-only", + "values": [ + { + "index": 0, + "value": "NVIDIA GeForce RTX 3070" + } + ] + }, + "nv_filters": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "module_loaded": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "output_scales": { + "applicability": "boot-fixed", + "canvas": { + "height": 1080, + "width": 1920 + }, + "values": [ + { + "height": 1080, + "scale": 1.0, + "value": "1920x1080", + "width": 1920 + } + ] + }, + "record_container": { + "applicability": "boot-fixed", + "value": "mp4", + "values": [ + { + "value": "mp4" + }, + { + "value": "mkv" + } + ] + }, + "record_markers": { + "add_chapter": false, + "applicability": "read-only", + "split_file": true + }, + "source_kinds": { + "applicability": "live", + "values": [ + { + "value": "audio_line" + }, + { + "value": "pulsar_preview_frame" + }, + { + "value": "image_source" + }, + { + "value": "color_source" + }, + { + "value": "color_source_v2" + }, + { + "value": "color_source_v3" + }, + { + "value": "slideshow" + }, + { + "value": "slideshow_v2" + }, + { + "value": "ffmpeg_source" + }, + { + "value": "text_gdiplus" + }, + { + "value": "text_gdiplus_v2" + }, + { + "value": "text_gdiplus_v3" + }, + { + "value": "browser_source" + }, + { + "value": "text_ft2_source" + }, + { + "value": "text_ft2_source_v2" + }, + { + "value": "vlc_source" + }, + { + "value": "monitor_capture" + }, + { + "value": "window_capture" + }, + { + "value": "game_capture" + }, + { + "value": "dshow_input" + }, + { + "value": "wasapi_input_capture" + }, + { + "value": "wasapi_output_capture" + }, + { + "value": "wasapi_process_output_capture" + } + ] + }, + "video_bitrate": { + "applicability": "live", + "max": 50000, + "min": 200, + "step": 50 + }, + "video_colorimetry": { + "applicability": "read-only", + "format": "NV12", + "range": "Partial", + "value": "709" + } + }, + "encoders": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ], + "version": 1, + "video_bitrate": { + "max": 50000, + "min": 200 + } + }, + "audio": { + "fixture": "seeded white noise, raw native volume-meter magnitude", + "baseline": 0.07981931346078072, + "methods": [ + { + "method": "denoiser", + "magnitude": 0.005526011408279719, + "liveIntensityZeroMagnitude": 1.7043338654928213e-05, + "disabledMagnitude": 0.07985600361637041, + "liveIntensityOneMagnitude": 0.005355545595458959, + "liveIntensityPassed": false + } + ] + }, + "failure": { + "type": "ConnectionClosedError", + "message": "no close frame received or sent" + }, + "shutdown": { + "exitCode": 3221226356, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T10:22:41Z INFO 9mCIqKHRRJva3B9f libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T10:22:41Z INFO 9mCIqKHRRJva3B9f libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T10:22:42Z INFO 9mCIqKHRRJva3B9f NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T10:22:42Z INFO 9mCIqKHRRJva3B9f NVIDIA Audio Effects: | enabled", + "2026-10-10T10:22:42Z INFO 9mCIqKHRRJva3B9f NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T10:22:42Z INFO 9mCIqKHRRJva3B9f NVIDIA VIDEO FX | : enabled, redistributable found", + "2026-10-10T10:22:46Z INFO 9mCIqKHRRJva3B9f NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK" + ], + "sdkDiagnostics": [], + "qualification": "synthetic functional/performance regression; SDK diagnostics require separate review", + "passed": false +} diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-report.md b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-report.md new file mode 100644 index 00000000..a6067950 --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T103031Z-report.md @@ -0,0 +1,94 @@ +# NVIDIA SDK : qualification native complémentaire + +## Verdict + +AFX 1.6.1.2 et VFX 0.7.6.0 sont installés et les quatre filtres se chargent. +La vidéo et les trois méthodes audio produisent un traitement réel. **La +qualification complète des intensités audio reste FAIL** : le débruitage simple +retourne un signal presque nul aux valeurs intermédiaires 0,25 et 0,75. +Le contrôle contemporain avec filtre désactivé prouve que la fixture continue. +Écho et méthode combinée répondent correctement aux intensités intermédiaires. + +Ce résultat ne vaut pas parité complète avec NVIDIA Broadcast. Aucun suivi, +Eye Contact, débruitage vidéo, éclairage IA ou Studio Voice n'est ajouté. + +## Périmètre et candidat + +Continuation directe, sans ADR, agent ou Computer Use : +`D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261009-capability-audit`, +branche `eleven/local-20261009-capability-audit`. Base c298c13f ; produit +`0cac057601e1836c57631d82cd563716569cec22`, signé et poussé, PR #301. + +DLL nv-filters qualifiée : +`59df7e4992d186d83dc5ad8415148b5533111c923336d9ca9dfd32ef92441c3b`. +Le moteur isolé est hashé dans le JSON. Build ciblé MSVC 19.44 / x64 des trois +sources nv-filters, configurations de chargement sécurisé conservées, SDK et +modèles fournis uniquement par l'installation officielle Program Files. +Le replay des deux nouveaux patches sur les fichiers NVIDIA du pin bd73b922 +correspond exactement au candidat compilé e2aca7894. Les patches antérieurs +ne modifient pas ces deux feuilles. Ce n'est pas un rebuild/CI du paquet entier. + +## Corrections + +- Patch 0060 : le flou charge après liaison de ses images, puis recharge sur + les changements live. Les statuts SDK échoués sont journalisés. Les callbacks + null/vides ne créent plus de faux messages ERROR ; tous les vrais messages + restent conservés. Le run final contient zéro diagnostic SDK ERROR/WARN. +- Patch 0061 : intensité zéro restitue les échantillons PCM originaux dans la + file native de 10 ms. Flag atomique, état enabled conservé, aucune allocation, + nouveau verrou ou réinitialisation de modèle dans le thread audio. Ce bypass + à zéro inclut VAD. Les valeurs non nulles restent traitées par le SDK. +- Sonde versionnée `scripts/probe-nvidia-effects.py` : hashes moteur/module, + pixels source et scène Program, paramètres live, contrôle désactivé audio, + valeurs intermédiaires, empilement et changements de scène. Le gate reste + négatif tant que le contrat d'intensité n'est pas respecté. + +## Preuves + +[Run natif final](20261010T103031Z-native.json) : 1920×1080 à 60 Hz, fixture +vidéo de bandes et bruit audio déterministe à 48 kHz, aucune caméra/microphone +physique, sortie réseau ou enregistrement utilisateur. + +| Contrôle | Résultat | +|---|---| +| Détourage IA | Alpha 0 sur fixture sans personne ; différence source et scène Program 137,625 | +| Flou image | Différence source et Program 58,8734 ; intensité zéro et bypass rendent les pixels initiaux exacts | +| Flou arrière-plan | Différence source et Program 58,8734 ; mêmes contrôles de bypass | +| Cadence VFX | 302/302/303 images par fenêtre de cinq secondes ; zéro image sautée | +| Rendu VFX moyen | 3,9545 / 1,7057 / 5,0465 ms sous charge locale, pas un coût GPU isolé | +| Empilement | Deux flous actifs ; 20 changements de scène natifs confirmés ; 243 images, zéro saut | +| Noise Removal | Magnitude 0,004041 contre entrée 0,079782 ; réduction réelle | +| Room Echo Removal | Modèle chargé et signal changé ; cette fixture ne prouve pas la qualité de suppression d'écho réel | +| Méthode combinée | Magnitude 0,004757 ; réduction réelle | +| Audio intensité 0 puis 1 | PASS pour les trois méthodes ; original restitué et effet réappliqué | +| Audio 0,75 puis 0,25 | PASS écho/combiné ; FAIL denoiser simple : 2,87e-6 puis 1,43e-6 | +| Retrait et arrêt | Filtres retirés ; exit 0, aucun arrêt forcé | +| Logs SDK | Aucun ERROR/WARN dans le candidat final | + +Le fichier JSON conserve `passed:false` et le message exact de la version de +sonde exécutée. Son ancien texte « zero is not passthrough » regroupe aussi le +sous-cas `denoiser/intermediate` ; la sonde courante nomme désormais correctement +« live intensity contract failed ». L'échec concerne les valeurs intermédiaires, +pas le bypass à zéro corrigé. Les chiffres sont des magnitudes de vu-mètre +natifs, pas des scores perceptuels ni un RMS calculé sur un fichier utilisateur. + +## Essais rejetés + +Une séparation des buffers entrée/sortie n'a pas corrigé le défaut ; elle +n'est pas retenue. [Essai reset](20261010T103031Z-rejected-reset.json) : reset +après changement d'intensité ne corrige pas le signal et termine le processus +isolé avec 0xC0000374. Aucun reset ajouté ne figure dans les patches ni dans +la DLL livrée. Le processus partagé n'a pas été utilisé pour ces essais. +[Contrôle initial](20261010T103031Z-audio-baseline.json) conserve le défaut zéro +avant patch 0061 ; il prouve la restauration immédiate par désactivation. + +## Validation et suite + +Build MSVC ciblé, replay des patches, compilation Python et contrôle packaging +PASS. Aucun SDK/modèle NVIDIA n'est intégré au paquet Pulsar. Les régressions +Prism et le constat Preview/transport appartiennent aux preuves Prism. + +Le curseur intermédiaire du denoiser simple reste ouvert. Ne pas qualifier +la qualité de parole, VAD sur de vrais locuteurs, modes chair, charge multi-caméra, +multi-GPU, durée longue ou chaîne RTMP depuis cette sonde. Les 20 switches sont +des changements libobs isolés, pas une campagne Prism→Orion de Takes. diff --git a/patches/README.md b/patches/README.md index 7e461239..b999228a 100644 --- a/patches/README.md +++ b/patches/README.md @@ -1,6 +1,6 @@ # Pulsar OBS patch stack -Pulsar 3.0.0 carries **54 patches** above the pinned +Pulsar 3.0.0 carries **58 patches** above the pinned `ZabLaboratory/obs-studio` revision `bd73b922891e56839b0bc86bdc519802802f9d68`. Five foundational changes are already in that fork revision. From ed9d4fdc5ebd322ad99abc54fd6e59b21a4584da Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Sat, 10 Oct 2026 13:10:52 +0200 Subject: [PATCH 13/17] fix(nvidia): apply live audio settings without blocking the audio thread --- README.md | 4 +- docs/LIBOBS-CHANGES.md | 18 +- .../20261010T111100Z-native-worker.json | 1060 +++++++++++++++++ .../eleven/20261010T111100Z-patch-replay.json | 6 + ...1010T111100Z-sdk-intensity-experiment.json | 130 ++ ...062-fix-nvidia-audio-settings-worker.patch | 434 +++++++ patches/README.md | 10 +- plugins/pulsar-nv-secure-load/README.md | 11 +- scripts/probe-nvidia-effects.py | 92 +- 9 files changed, 1756 insertions(+), 9 deletions(-) create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-native-worker.json create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-patch-replay.json create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-sdk-intensity-experiment.json create mode 100644 patches/0062-fix-nvidia-audio-settings-worker.patch diff --git a/README.md b/README.md index 5ffc95fc..c821da97 100644 --- a/README.md +++ b/README.md @@ -473,7 +473,7 @@ does not recover scene identity from arbitrary pixels. Pulsar is a maintained native fork, not only a JavaScript wrapper around stock OBS. Its OBS submodule is pinned to `bd73b922891e56839b0bc86bdc519802802f9d68`, an OBS 32.1.2-derived revision. -Five foundational modifications are incorporated in that pin; **58 additional +Five foundational modifications are incorporated in that pin; **59 additional patch files** build the current media core. | Area | What Pulsar changes | @@ -598,7 +598,7 @@ as a troubleshooting shortcut. | [Client](packages/pulsar-client/README.md) | TypeScript API, typed events, errors and examples. | | [Embedding](docs/PRISM-EMBEDDING.md) | Host lifecycle, readiness and verified bundle integration. | | [Architecture](docs/ARCHITECTURE.md) | Runtime structure and ownership boundaries. | -| [All libobs changes](docs/LIBOBS-CHANGES.md) | Integrated OBS changes and all 58 source patches. | +| [All libobs changes](docs/LIBOBS-CHANGES.md) | Integrated OBS changes and all 59 source patches. | | [All documentation](docs/README.md) | Current guides, operational runbooks, contracts and historical studies. | | [Development](docs/DEVELOPMENT.md) | Build toolchain and local development. | | [Dual-lane canary](docs/runbooks/pulsar-dual-lane-canary.md) | Qualification and operational checks. | diff --git a/docs/LIBOBS-CHANGES.md b/docs/LIBOBS-CHANGES.md index ca6fc8f2..aab22d82 100644 --- a/docs/LIBOBS-CHANGES.md +++ b/docs/LIBOBS-CHANGES.md @@ -28,7 +28,7 @@ missing files to recreate in `patches/`: | [7e7a5a383](https://github.com/ZabLaboratory/obs-studio/commit/7e7a5a383) | Adds a separate Program-return camera. | | [b33f831b8](https://github.com/ZabLaboratory/obs-studio/commit/b33f831b8) | Resolves embedded modules and registers Program return. | -On top of this pin, 3.0.0 carries **58 patch files**: 57 root-OBS patches +On top of this pin, 3.0.0 carries **59 patch files**: 58 root-OBS patches and one nested obs-browser patch. [build-win.ps1](../scripts/build-win.ps1) sorts complete filenames lexically, routes names containing `obs-browser` to `upstream/plugins/obs-browser/`, and applies the others to `upstream/`. @@ -607,6 +607,22 @@ contemporaneous disabled-filter control and reapplies intensity one. Affected file: `plugins/nv-filters/nvidia-audiofx-filter.c`. +### 0062-fix-nvidia-audio-settings-worker + +[Source patch](../patches/0062-fix-nvidia-audio-settings-worker.patch) + +Recreates private AFX handles off the audio/settings thread when method, +intensity or VAD changes. The installed AFX denoiser captures intensity at +load and VAD is a load-time SDK parameter. Settings coalesce on a persistent +worker; original PCM passes through the existing FIFO during loading. +The audio callback never waits for model loading. Destruction joins the worker +outside its mutex before releasing buffers or handles. The global SDK logger +lives until module unload rather than an individual filter's destruction. +The probe adds 72 rapid settings changes, six removals during loading, two +audio filters and optional native recording of synthetic fixtures. + +Affected file: `plugins/nv-filters/nvidia-audiofx-filter.c`. + ## Validation boundaries and retained studies The native CPU readback study records 100 warm-up and 100 measured Cuts per diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-native-worker.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-native-worker.json new file mode 100644 index 00000000..367e01e4 --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-native-worker.json @@ -0,0 +1,1060 @@ +{ + "started": 1791630488.3975897, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "nvFiltersSha256": "e52040f52b9510ec34afd3e142df276b0252745f8713b655b8943d27f40b1365", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "mutedDeviceInputs": [ + "PulsarDesktopAudio" + ], + "capabilities": { + "active_encoder": "nvenc", + "audio_bitrate": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ], + "capabilities": { + "active_encoder": { + "applicability": "boot-fixed", + "value": "nvenc" + }, + "audio_bitrate": { + "applicability": "live", + "max": 512, + "min": 64, + "step": 32, + "values": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ] + }, + "audio_monitoring": { + "applicability": "live", + "available": true, + "device_bound": true, + "device_id": "{0.0.0.00000000}.{3a9b3ef5-04ae-4b95-88d7-53e24294bc68}", + "device_name": "Default", + "device_selectable": true + }, + "audio_sample_rate": { + "applicability": "read-only", + "hz": 48000 + }, + "audio_speaker_layout": { + "applicability": "read-only", + "channels": 2, + "layout": "stereo" + }, + "audio_tracks": { + "applicability": "read-only", + "bound": 1, + "count": 6, + "tracks": [ + { + "value": 1 + } + ] + }, + "destination_kinds": { + "applicability": "live", + "values": [ + { + "value": "rtmp_custom" + }, + { + "value": "vod_local" + }, + { + "value": "twitch" + }, + { + "value": "youtube" + } + ] + }, + "encoder_families": { + "applicability": "boot-fixed", + "values": [ + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 20, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "ultrafast" + }, + { + "value": "superfast" + }, + { + "value": "veryfast" + }, + { + "value": "faster" + }, + { + "value": "fast" + }, + { + "value": "medium" + }, + { + "value": "slow" + }, + { + "value": "slower" + }, + { + "value": "veryslow" + }, + { + "value": "placebo" + } + ], + "profiles": [ + { + "value": "baseline" + }, + { + "value": "main" + }, + { + "value": "high" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "VBR" + } + ], + "value": "x264" + }, + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 10, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "p1" + }, + { + "value": "p2" + }, + { + "value": "p3" + }, + { + "value": "p4" + }, + { + "value": "p5" + }, + { + "value": "p6" + }, + { + "value": "p7" + } + ], + "profiles": [ + { + "value": "high" + }, + { + "value": "main" + }, + { + "value": "baseline" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "CQP" + }, + { + "value": "VBR" + } + ], + "value": "nvenc" + } + ] + }, + "encoders": { + "applicability": "boot-fixed", + "values": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ] + }, + "filters": { + "applicability": "live", + "values": [ + { + "value": "nvidia_audiofx_filter" + }, + { + "value": "nv_greenscreen_filter" + }, + { + "value": "nv_blur_filter" + }, + { + "value": "nv_background_blur_filter" + }, + { + "value": "mask_filter" + }, + { + "value": "mask_filter_v2" + }, + { + "value": "crop_filter" + }, + { + "value": "gain_filter" + }, + { + "value": "basic_eq_filter" + }, + { + "value": "hdr_tonemap_filter" + }, + { + "value": "color_filter" + }, + { + "value": "color_filter_v2" + }, + { + "value": "scale_filter" + }, + { + "value": "scroll_filter" + }, + { + "value": "gpu_delay" + }, + { + "value": "color_key_filter" + }, + { + "value": "color_key_filter_v2" + }, + { + "value": "clut_filter" + }, + { + "value": "sharpness_filter" + }, + { + "value": "sharpness_filter_v2" + }, + { + "value": "chroma_key_filter" + }, + { + "value": "chroma_key_filter_v2" + }, + { + "value": "async_delay_filter" + }, + { + "value": "noise_suppress_filter" + }, + { + "value": "noise_suppress_filter_v2" + }, + { + "value": "invert_polarity_filter" + }, + { + "value": "noise_gate_filter" + }, + { + "value": "compressor_filter" + }, + { + "value": "limiter_filter" + }, + { + "value": "expander_filter" + }, + { + "value": "upward_compressor_filter" + }, + { + "value": "luma_key_filter" + }, + { + "value": "luma_key_filter_v2" + }, + { + "value": "pulsar_vignette_filter" + } + ] + }, + "graphics_adapters": { + "active_index": 0, + "applicability": "read-only", + "values": [ + { + "index": 0, + "value": "NVIDIA GeForce RTX 3070" + } + ] + }, + "nv_filters": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "module_loaded": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "output_scales": { + "applicability": "boot-fixed", + "canvas": { + "height": 1080, + "width": 1920 + }, + "values": [ + { + "height": 1080, + "scale": 1.0, + "value": "1920x1080", + "width": 1920 + } + ] + }, + "record_container": { + "applicability": "boot-fixed", + "value": "mp4", + "values": [ + { + "value": "mp4" + }, + { + "value": "mkv" + } + ] + }, + "record_markers": { + "add_chapter": false, + "applicability": "read-only", + "split_file": true + }, + "source_kinds": { + "applicability": "live", + "values": [ + { + "value": "audio_line" + }, + { + "value": "pulsar_preview_frame" + }, + { + "value": "image_source" + }, + { + "value": "color_source" + }, + { + "value": "color_source_v2" + }, + { + "value": "color_source_v3" + }, + { + "value": "slideshow" + }, + { + "value": "slideshow_v2" + }, + { + "value": "ffmpeg_source" + }, + { + "value": "text_gdiplus" + }, + { + "value": "text_gdiplus_v2" + }, + { + "value": "text_gdiplus_v3" + }, + { + "value": "browser_source" + }, + { + "value": "text_ft2_source" + }, + { + "value": "text_ft2_source_v2" + }, + { + "value": "vlc_source" + }, + { + "value": "monitor_capture" + }, + { + "value": "window_capture" + }, + { + "value": "game_capture" + }, + { + "value": "dshow_input" + }, + { + "value": "wasapi_input_capture" + }, + { + "value": "wasapi_output_capture" + }, + { + "value": "wasapi_process_output_capture" + } + ] + }, + "video_bitrate": { + "applicability": "live", + "max": 50000, + "min": 200, + "step": 50 + }, + "video_colorimetry": { + "applicability": "read-only", + "format": "NV12", + "range": "Partial", + "value": "709" + } + }, + "encoders": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ], + "version": 1, + "video_bitrate": { + "max": 50000, + "min": 200 + } + }, + "video": [ + { + "kind": "nv_greenscreen_filter", + "settings": { + "mode": 1, + "processing_interval": 1, + "threshold": 0.8 + }, + "pixelDifferenceMean": 137.625, + "alphaRange": [ + 0, + 0 + ], + "frameDelta": 301, + "skippedDelta": 0, + "meanRenderMs": 4.169411, + "programScenePixelDifferenceMean": 137.625 + }, + { + "kind": "nv_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 304, + "skippedDelta": 0, + "meanRenderMs": 2.0705424, + "programScenePixelDifferenceMean": 58.8734375, + "liveIntensity": [ + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.2, + "pixelDifferenceMean": 58.86875 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + }, + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + } + ] + }, + { + "kind": "nv_background_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 304, + "skippedDelta": 0, + "meanRenderMs": 5.4707944, + "programScenePixelDifferenceMean": 58.8734375, + "liveIntensity": [ + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.2, + "pixelDifferenceMean": 58.86875 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + }, + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + } + ] + } + ], + "videoTimeline": [ + { + "seconds": 0.0, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_greenscreen_filter - traitement actif (fixture synthetique)" + }, + { + "seconds": 8.781000000002678, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_blur_filter - traitement actif (fixture synthetique)" + }, + { + "seconds": 20.452999999994063, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_background_blur_filter - traitement actif (fixture synthetique)" + }, + { + "seconds": 34.25, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 1/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 34.46899999999732, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 2/20 : NvidiaSDKRegression" + }, + { + "seconds": 34.687999999994645, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 3/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 34.89100000000326, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 4/20 : NvidiaSDKRegression" + }, + { + "seconds": 35.09399999999732, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 5/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 35.29700000000594, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 6/20 : NvidiaSDKRegression" + }, + { + "seconds": 35.5, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 7/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 35.70299999999406, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 8/20 : NvidiaSDKRegression" + }, + { + "seconds": 35.90600000000268, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 9/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 36.11000000000058, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 10/20 : NvidiaSDKRegression" + }, + { + "seconds": 36.312999999994645, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 11/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 36.51600000000326, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 12/20 : NvidiaSDKRegression" + }, + { + "seconds": 36.71899999999732, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 13/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 36.92200000000594, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 14/20 : NvidiaSDKRegression" + }, + { + "seconds": 37.125, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 15/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 37.32799999999406, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 16/20 : NvidiaSDKRegression" + }, + { + "seconds": 37.53100000000268, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 17/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 37.75, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 18/20 : NvidiaSDKRegression" + }, + { + "seconds": 37.96899999999732, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 19/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 38.187999999994645, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 20/20 : NvidiaSDKRegression" + }, + { + "seconds": 41.562999999994645, + "text": "NVIDIA / Pulsar - audio synthetique 48 kHz\nSignal original : magnitude native 0.07978" + }, + { + "seconds": 41.562999999994645, + "text": "NVIDIA AFX / denoiser\nIntensite 100 % / VAD OFF - bruit synthetique, aucune voix" + }, + { + "seconds": 49.64100000000326, + "text": "NVIDIA AFX / denoiser - intensite 75%\nMagnitude native : 0.02561 / original : 0.07978" + }, + { + "seconds": 52.79700000000594, + "text": "NVIDIA AFX / denoiser - intensite 25%\nMagnitude native : 0.06045 / original : 0.07978" + }, + { + "seconds": 55.95299999999406, + "text": "NVIDIA AFX / denoiser - intensite 0 %\nBypass PCM : 0.07933 / original : 0.07978" + }, + { + "seconds": 61.48500000000058, + "text": "NVIDIA AFX / dereverb\nIntensite 100 % / VAD OFF - bruit synthetique, aucune voix" + }, + { + "seconds": 69.65600000000268, + "text": "NVIDIA AFX / dereverb - intensite 75%\nMagnitude native : 0.03372 / original : 0.07978" + }, + { + "seconds": 72.79700000000594, + "text": "NVIDIA AFX / dereverb - intensite 25%\nMagnitude native : 0.06187 / original : 0.07978" + }, + { + "seconds": 75.95299999999406, + "text": "NVIDIA AFX / dereverb - intensite 0 %\nBypass PCM : 0.07872 / original : 0.07978" + }, + { + "seconds": 81.73500000000058, + "text": "NVIDIA AFX / dereverb_denoiser\nIntensite 100 % / VAD OFF - bruit synthetique, aucune voix" + }, + { + "seconds": 89.89100000000326, + "text": "NVIDIA AFX / dereverb_denoiser - intensite 75%\nMagnitude native : 0.02423 / original : 0.07978" + }, + { + "seconds": 92.89100000000326, + "text": "NVIDIA AFX / dereverb_denoiser - intensite 25%\nMagnitude native : 0.05906 / original : 0.07978" + }, + { + "seconds": 96.04700000000594, + "text": "NVIDIA AFX / dereverb_denoiser - intensite 0 %\nBypass PCM : 0.07932 / original : 0.07978" + }, + { + "seconds": 121.36000000000058, + "text": "NVIDIA / Pulsar - fin de qualification\n72 mises a jour rapides / retrait pendant chargement / deux filtres audio" + } + ], + "stackedSceneSwitches": [ + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + } + ], + "stackedStats": { + "frameDelta": 248, + "skippedDelta": 0, + "lastRenderMs": 8.32494 + }, + "audio": { + "fixture": "seeded white noise, raw native volume-meter magnitude", + "baseline": 0.07978055308588215, + "methods": [ + { + "method": "denoiser", + "magnitude": 0.005828781508209709, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.02560810536398726 + }, + { + "intensity": 0.25, + "magnitude": 0.06044548362373344 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.07932975918787424, + "disabledMagnitude": 0.07985237742565116, + "liveIntensityOneMagnitude": 0.01364456278016662, + "liveIntensityPassed": true + }, + { + "method": "dereverb", + "magnitude": 0.031255029427001674, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.03371609971079914 + }, + { + "intensity": 0.25, + "magnitude": 0.061873698698692636 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.07871925746721606, + "disabledMagnitude": 0.07994747710115505, + "liveIntensityOneMagnitude": 0.02631846787866022, + "liveIntensityPassed": true + }, + { + "method": "dereverb_denoiser", + "magnitude": 0.005036673956858591, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.024226004678395488 + }, + { + "intensity": 0.25, + "magnitude": 0.059058816124827176 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.07932013777955886, + "disabledMagnitude": 0.08009461356470218, + "liveIntensityOneMagnitude": 0.009182564417240328, + "liveIntensityPassed": true + } + ], + "liveIntensityPassed": true + }, + "audioLifecycle": [ + { + "cycle": 0, + "updates": 12, + "maxUpdateMs": 0.41140000394079834, + "removeMs": 1.404700000421144 + }, + { + "cycle": 1, + "updates": 12, + "maxUpdateMs": 0.4400999896461144, + "removeMs": 1.2119000020902604 + }, + { + "cycle": 2, + "updates": 12, + "maxUpdateMs": 0.8256000000983477, + "removeMs": 1.563400001032278 + }, + { + "cycle": 3, + "updates": 12, + "maxUpdateMs": 0.4048999981023371, + "removeMs": 2.3280000023078173 + }, + { + "cycle": 4, + "updates": 12, + "maxUpdateMs": 0.3273999900557101, + "removeMs": 1.177600002847612 + }, + { + "cycle": 5, + "updates": 12, + "maxUpdateMs": 0.4710999928647652, + "removeMs": 2.3855000035837293 + } + ], + "audioSurvivingFilterMagnitude": 0.039179178386381484, + "audioLiveVad": { + "enabledMagnitude": 0.059391372754604, + "disabledMagnitude": 0.059984089641631405, + "enabledSettings": { + "intensity": 0.25, + "method": "denoiser", + "vad": true + }, + "disabledSettings": { + "intensity": 0.25, + "method": "denoiser", + "vad": false + }, + "scope": "live reload/readback and signal continuity; white noise cannot qualify voice detection accuracy", + "zeroWithVadMagnitude": 0.07949330064199739 + }, + "nativeRecording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-qualified-recording\\pulsar-20261010-130810.mp4", + "checks": [ + "SDK usable/version", + "four filters registered", + "three VFX source and Program scene pixel changes and exact bypass", + "live VFX intensity", + "20 scene switches with stacked VFX", + "native render timings", + "three AFX model methods", + "noise suppression signal", + "filter removal" + ], + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T11:08:08Z INFO _yXrQ9HjZTZT3izY libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T11:08:08Z INFO _yXrQ9HjZTZT3izY libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T11:08:08Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T11:08:09Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: | enabled", + "2026-10-10T11:08:09Z INFO _yXrQ9HjZTZT3izY NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T11:08:09Z INFO _yXrQ9HjZTZT3izY NVIDIA VIDEO FX | : enabled, redistributable found", + "2026-10-10T11:08:52Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:09:12Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:09:32Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:09:52Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:09:52Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:09:52Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:09:52Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:09:52Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:09:52Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:09:53Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/first' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:09:53Z INFO _yXrQ9HjZTZT3izY NVIDIA Audio Effects: 'sdk/second' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK" + ], + "sdkDiagnostics": [], + "qualification": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity", + "passed": true +} diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-patch-replay.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-patch-replay.json new file mode 100644 index 00000000..1966502d --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-patch-replay.json @@ -0,0 +1,6 @@ +{ + "passed": true, + "pin": "bd73b922891e56839b0bc86bdc519802802f9d68", + "candidate": "6fc5905315af589cde8dc1f7c85212d5ee6ad65a", + "check": "The three NVIDIA patches replay in order on pinned native sources and match the compiled candidate. Earlier patch stack has no nv-filters changes." +} diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-sdk-intensity-experiment.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-sdk-intensity-experiment.json new file mode 100644 index 00000000..c071f1dc --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-sdk-intensity-experiment.json @@ -0,0 +1,130 @@ +[ + { + "disableCudaGraph": false, + "initial": 1.0, + "loadSeconds": 0.15700000000651926, + "steps": [ + { + "ratio": 1.0, + "rms": 3.1057445539772743e-06, + "runMeanMs": 0.2724999999918509 + }, + { + "ratio": 0.75, + "rms": 1.617224256733607e-06, + "runMeanMs": 0.23499999999330612 + }, + { + "ratio": 0.25, + "rms": 2.20515994442394e-06, + "runMeanMs": 0.23250000002008164 + }, + { + "ratio": 0.0, + "rms": 2.5127171738859033e-06, + "runMeanMs": 0.1974999999947613 + }, + { + "ratio": 1.0, + "rms": 2.777113195406855e-06, + "runMeanMs": 0.19499999998515705 + } + ] + }, + { + "disableCudaGraph": false, + "initial": 0.25, + "loadSeconds": 0.07800000000861473, + "steps": [ + { + "ratio": 0.25, + "rms": 0.059942033141851425, + "runMeanMs": 0.23499999999330612 + }, + { + "ratio": 0.75, + "rms": 0.059942010790109634, + "runMeanMs": 0.19499999998515705 + }, + { + "ratio": 0.25, + "rms": 0.05994201451539993, + "runMeanMs": 0.19500000002153683 + }, + { + "ratio": 0.0, + "rms": 0.05994201451539993, + "runMeanMs": 0.23499999999330612 + }, + { + "ratio": 1.0, + "rms": 0.05994201451539993, + "runMeanMs": 0.23499999999330612 + } + ] + }, + { + "disableCudaGraph": true, + "initial": 1.0, + "loadSeconds": 0.07800000000861473, + "steps": [ + { + "ratio": 1.0, + "rms": 3.1057445539772743e-06, + "runMeanMs": 0.5849999999918509 + }, + { + "ratio": 0.75, + "rms": 1.617224256733607e-06, + "runMeanMs": 0.625 + }, + { + "ratio": 0.25, + "rms": 2.20515994442394e-06, + "runMeanMs": 0.5875000000014552 + }, + { + "ratio": 0.0, + "rms": 2.5127171738859033e-06, + "runMeanMs": 0.5849999999918509 + }, + { + "ratio": 1.0, + "rms": 2.777113195406855e-06, + "runMeanMs": 0.6650000000081491 + } + ] + }, + { + "disableCudaGraph": true, + "initial": 0.25, + "loadSeconds": 0.07899999999790452, + "steps": [ + { + "ratio": 0.25, + "rms": 0.059942033141851425, + "runMeanMs": 0.5449999999837019 + }, + { + "ratio": 0.75, + "rms": 0.059942010790109634, + "runMeanMs": 0.5875000000014552 + }, + { + "ratio": 0.25, + "rms": 0.05994201451539993, + "runMeanMs": 0.5850000000282307 + }, + { + "ratio": 0.0, + "rms": 0.05994201451539993, + "runMeanMs": 0.5474999999933061 + }, + { + "ratio": 1.0, + "rms": 0.05994201451539993, + "runMeanMs": 0.5849999999918509 + } + ] + } +] \ No newline at end of file diff --git a/patches/0062-fix-nvidia-audio-settings-worker.patch b/patches/0062-fix-nvidia-audio-settings-worker.patch new file mode 100644 index 00000000..035e3392 --- /dev/null +++ b/patches/0062-fix-nvidia-audio-settings-worker.patch @@ -0,0 +1,434 @@ +From 6fc5905315af589cde8dc1f7c85212d5ee6ad65a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?ClodoCap=C3=A9o?= + <159788250+ClodoCapeo@users.noreply.github.com> +Date: Sat, 10 Oct 2026 12:59:56 +0200 +Subject: [PATCH] fix(nv-filters): reload audio settings off the real-time + thread + +AFX 1.6.1.2 captures denoiser intensity and VAD at model load. Live +SetFloat readback does not prove processing changed. Recreate handles +on a persistent worker when settings change, coalescing rapid requests. +The audio callback uses trylock and preserves original PCM/FIFO timing +during loading rather than waiting for CUDA/model initialization. + +Join the worker before releasing its data, outside the loader mutex. +Keep the global SDK logger until module unload, independently of filter +lifetimes. Destroy each actual channel handle, including partial loads. +Zero intensity keeps the atomic PCM bypass from patch 0061. + +This modifies nv-filters' private SDK handles and audio pipeline; an +external plugin cannot intercept it. Eligible for upstream OBS review. +Work unit: Eleven local-20261010-nvidia-sdk-integration, Pulsar PR #301. +Validation: installed-SDK native intensity oracle, rapid updates, VAD +changes, removal while loading, multiple filters and clean shutdown. +--- + plugins/nv-filters/nvidia-audiofx-filter.c | 239 +++++++++------------ + 1 file changed, 104 insertions(+), 135 deletions(-) + +diff --git a/plugins/nv-filters/nvidia-audiofx-filter.c b/plugins/nv-filters/nvidia-audiofx-filter.c +index 85150ed0b..36471ef11 100644 +--- a/plugins/nv-filters/nvidia-audiofx-filter.c ++++ b/plugins/nv-filters/nvidia-audiofx-filter.c +@@ -50,6 +50,7 @@ + #endif + + bool nvafx_new_sdk = false; ++static void nvafx_logger_callback(void *data, const char *msg); + + struct nvidia_audio_data { + obs_source_t *context; +@@ -64,20 +65,8 @@ struct nvidia_audio_data { + struct deque input_buffers[MAX_PREPROC_CHANNELS]; + struct deque output_buffers[MAX_PREPROC_CHANNELS]; + +- /* This bool is quite important but it is easy to get lost. So let's +- * explain how it's used. One big issue is that the NVIDIA FX takes +- * ages to load an FX; so its initialization is deferred to a separate +- * thread. +- * First stage (creation): +- * - use_nvafx is set to true at creation of the filter, IF the SDK dir +- * path is set. +- * - if initialization fails, the bool is set to false & the filter is +- * destroyed. +- * Later stages (running or updating of the FX): +- * - they are executed ONLY if initialization was successful; +- * - if at any step, there's an FX failure, the bool is updated to false +- * & the filter is destroyed. +- */ ++ /* Owned by the configuration worker and read under nvafx_mutex. ++ * Failed initialization keeps original PCM until a later retry. */ + bool use_nvafx; + volatile bool bypass_nvafx; + +@@ -94,7 +83,7 @@ struct nvidia_audio_data { + float intensity_ratio; + unsigned int num_samples_per_frame, num_channels; + char *model; +- bool nvafx_initialized; ++ volatile bool nvafx_initialized; + const char *fx; + char *sdk_path; + +@@ -102,12 +91,16 @@ struct nvidia_audio_data { + audio_resampler_t *nvafx_resampler; + audio_resampler_t *nvafx_resampler_back; + +- /* We load the DLL in a separate thread because its loading is very +- * long and unnecessarily blocks OBS initial loading. +- * This bool is true once the thread which side loads the FX DLL is started */ +- bool nvafx_loading; ++ /* Persistent control worker; audio never waits for model loading. */ + pthread_t nvafx_thread; + pthread_mutex_t nvafx_mutex; ++ pthread_mutex_t settings_mutex; ++ pthread_cond_t settings_changed; ++ bool worker_started, stopping; ++ uint64_t settings_generation; ++ char *requested_fx; ++ float requested_intensity; ++ bool requested_vad; + + /* PCM buffers */ + float *copy_buffers[MAX_PREPROC_CHANNELS]; +@@ -134,14 +127,18 @@ static void nvidia_audio_destroy(void *data) + if (!ng) + return; + +- if (ng->nvidia_sdk_dir_found) ++ /* Never join while holding the mutex needed by the loader. */ ++ if (ng->nvidia_sdk_dir_found) { ++ pthread_mutex_lock(&ng->settings_mutex); ++ ng->stopping = true; ++ pthread_cond_signal(&ng->settings_changed); ++ pthread_mutex_unlock(&ng->settings_mutex); ++ if (ng->worker_started) ++ pthread_join(ng->nvafx_thread, NULL); + pthread_mutex_lock(&ng->nvafx_mutex); +- +- if (nvafx_new_sdk) +- NvAFX_UninitializeLogger(); +- ++ } + for (size_t i = 0; i < ng->channels; i++) { +- if (ng->handle[0]) { ++ if (ng->handle[i]) { + if (NvAFX_DestroyEffect) { + NvAFX_Status err = NvAFX_DestroyEffect(ng->handle[i]); + if (err != NVAFX_STATUS_SUCCESS) { +@@ -163,9 +160,11 @@ static void nvidia_audio_destroy(void *data) + bfree(ng->sdk_path); + bfree((void *)ng->fx); + if (ng->nvidia_sdk_dir_found) { +- pthread_join(ng->nvafx_thread, NULL); + pthread_mutex_unlock(&ng->nvafx_mutex); + pthread_mutex_destroy(&ng->nvafx_mutex); ++ pthread_cond_destroy(&ng->settings_changed); ++ pthread_mutex_destroy(&ng->settings_mutex); ++ bfree(ng->requested_fx); + } + + bfree(ng->copy_buffers[0]); +@@ -335,6 +334,9 @@ bool load_nvidia_afx(void) + } + + nvidia_afx_loaded = true; ++ if (nvafx_new_sdk && NvAFX_InitializeLogger(NVAFX_LOG_LEVEL_ERROR, LOG_TARGET_CALLBACK, NULL, ++ &nvafx_logger_callback, NULL) != NVAFX_STATUS_SUCCESS) ++ blog(LOG_WARNING, "[NVIDIA Audio Effects:] logger failed to initialize"); + blog(LOG_INFO, "[NVIDIA Audio Effects:] enabled"); + return true; + +@@ -351,6 +353,8 @@ unload_everything: + + void unload_nvidia_afx(void) + { ++ if (nvafx_new_sdk && NvAFX_UninitializeLogger) ++ NvAFX_UninitializeLogger(); + release_afxlib(); + + if (nvidia_afx_initializer_mutex_initialized) { +@@ -439,15 +443,6 @@ failure: + return false; + } + +-static void *nvidia_audio_disable(void *data) +-{ +- struct nvidia_audio_data *ng = data; +- obs_source_t *filter = ng->context; +- obs_source_set_enabled(filter, false); +- info("NVIDIA Audio FX disabled due to an internal error."); +- return NULL; +-} +- + static void *nvidia_audio_initialize(void *data) + { + struct nvidia_audio_data *ng = data; +@@ -456,7 +451,6 @@ static void *nvidia_audio_initialize(void *data) + if (!ng->use_nvafx && !nvidia_afx_loaded) { + return NULL; + } +- pthread_mutex_lock(&ng->nvafx_mutex); + pthread_mutex_lock(&nvidia_afx_initializer_mutex); + if (!nvidia_audio_initialize_internal(data)) { + goto failure; +@@ -484,17 +478,15 @@ static void *nvidia_audio_initialize(void *data) + goto failure; + } + } +- ng->nvafx_initialized = true; +- ng->nvafx_loading = false; ++ os_atomic_set_bool(&ng->nvafx_initialized, true); + pthread_mutex_unlock(&nvidia_afx_initializer_mutex); +- pthread_mutex_unlock(&ng->nvafx_mutex); + return NULL; + + failure: + ng->use_nvafx = false; ++ os_atomic_set_bool(&ng->nvafx_initialized, false); + pthread_mutex_unlock(&nvidia_afx_initializer_mutex); +- pthread_mutex_unlock(&ng->nvafx_mutex); +- nvidia_audio_disable(ng); ++ info("NVIDIA Audio FX initialization failed; preserving original PCM."); + return NULL; + } + +@@ -566,94 +558,67 @@ static void set_nv_model(void *data, const char *method) + ng->model = buffer; + } + +-static void nvidia_audio_update(void *data, obs_data_t *s) ++/* The installed AFX denoiser captures intensity at NvAFX_Load. Recreate ++ * on a persistent control worker, never on the audio or settings thread. ++ * Updates coalesce to the latest requested configuration. While a model is ++ * loading, the audio callback passes original PCM through its existing FIFO. */ ++static void *nvidia_audio_worker(void *data) + { + struct nvidia_audio_data *ng = data; +- +- if (!ng->use_nvafx) +- return; +- +- const char *method = obs_data_get_string(s, S_METHOD); +- ng->latency = 1000000000LL / (1000 / BUFFER_SIZE_MSEC); +- +- float intensity = (float)obs_data_get_double(s, S_NVAFX_INTENSITY); +- /* SDK 1.6.1.2 denoiser does not pass through at live intensity zero. +- * Use the original PCM through the existing 10 ms FIFO instead. This +- * is independent of the source's enabled state and needs no model +- * reload/reset or allocation on the audio thread. */ +- os_atomic_set_bool(&ng->bypass_nvafx, intensity == 0.0f); +- bool vad = obs_data_get_bool(s, S_NVAFX_VAD); +- +- /*-------------------------------------------------------------------*/ +- /* STAGE 1 : the following is run only when the filter is created. */ +- +- /* If the DLL hasn't been loaded & isn't loading, start the side loading. */ +- if (!ng->nvafx_initialized && !ng->nvafx_loading) { +- ng->intensity_ratio = intensity; +- ng->vad = vad; +- ng->nvafx_loading = true; +- pthread_create(&ng->nvafx_thread, NULL, nvidia_audio_initialize, ng); +- } +- +- /*-------------------------------------------------------------------*/ +- /* STAGE 2 : this is executed only after the FX has been initialized */ +- if (ng->nvafx_initialized) { +- /* updating the intensity of the FX */ +- if (intensity != ng->intensity_ratio && (strcmp(ng->fx, method) == 0)) { +- NvAFX_Status err; +- ng->intensity_ratio = intensity; +- pthread_mutex_lock(&ng->nvafx_mutex); +- for (size_t i = 0; i < ng->channels; i++) { +- err = NvAFX_SetFloat(ng->handle[i], NVAFX_PARAM_INTENSITY_RATIO, ng->intensity_ratio); +- if (err != NVAFX_STATUS_SUCCESS) { +- do_log(LOG_ERROR, "NvAFX_SetFloat(Intensity Ratio: %f) failed, error %i", +- ng->intensity_ratio, err); +- nvidia_audio_disable(ng); +- } +- } +- pthread_mutex_unlock(&ng->nvafx_mutex); +- } +- /* updating for VAD toggled on or off */ +- if (nvafx_new_sdk) { +- if (vad != ng->vad && (strcmp(ng->fx, method) == 0)) { +- NvAFX_Status err; +- ng->vad = vad; +- pthread_mutex_lock(&ng->nvafx_mutex); +- for (size_t i = 0; i < ng->channels; i++) { +- err = NvAFX_SetU32(ng->handle[i], NVAFX_PARAM_ENABLE_VAD, ng->vad); +- if (err != NVAFX_STATUS_SUCCESS) { +- do_log(LOG_ERROR, "NvAFX_SetU32(VAD: %i) failed, error %i", ng->vad, +- err); +- nvidia_audio_disable(ng); +- } +- } +- pthread_mutex_unlock(&ng->nvafx_mutex); +- } ++ uint64_t applied = 0; ++ for (;;) { ++ pthread_mutex_lock(&ng->settings_mutex); ++ while (!ng->stopping && applied == ng->settings_generation) ++ pthread_cond_wait(&ng->settings_changed, &ng->settings_mutex); ++ if (ng->stopping) { ++ pthread_mutex_unlock(&ng->settings_mutex); ++ break; + } +- /* swapping to a new FX requires a reinitialization */ +- if ((strcmp(ng->fx, method) != 0)) { +- pthread_mutex_lock(&ng->nvafx_mutex); +- bfree((void *)ng->fx); +- ng->fx = bstrdup(method); +- ng->intensity_ratio = intensity; +- ng->vad = vad; +- set_nv_model(ng, method); +- os_atomic_set_bool(&ng->reinit_done, false); +- for (int i = 0; i < (int)ng->channels; i++) { +- /* Destroy previous FX */ +- if (NvAFX_DestroyEffect(ng->handle[i]) != NVAFX_STATUS_SUCCESS) { +- do_log(LOG_ERROR, "FX failed to be destroyed."); +- nvidia_audio_disable(ng); +- } else { +- ng->handle[i] = NULL; +- } +- } +- if (!nvidia_audio_initialize_internal(data)) +- nvidia_audio_disable(ng); ++ uint64_t generation = ng->settings_generation; ++ char *method = bstrdup(ng->requested_fx); ++ float intensity = ng->requested_intensity; ++ bool vad = ng->requested_vad; ++ pthread_mutex_unlock(&ng->settings_mutex); + +- pthread_mutex_unlock(&ng->nvafx_mutex); ++ pthread_mutex_lock(&ng->nvafx_mutex); ++ os_atomic_set_bool(&ng->nvafx_initialized, false); ++ os_atomic_set_bool(&ng->reinit_done, false); ++ for (size_t i = 0; i < ng->channels; i++) { ++ if (ng->handle[i]) ++ NvAFX_DestroyEffect(ng->handle[i]); ++ ng->handle[i] = NULL; + } ++ bfree((void *)ng->fx); ++ ng->fx = method; ++ ng->intensity_ratio = intensity; ++ ng->vad = vad; ++ ng->use_nvafx = true; ++ set_nv_model(ng, method); ++ nvidia_audio_initialize(ng); ++ pthread_mutex_unlock(&ng->nvafx_mutex); ++ applied = generation; + } ++ return NULL; ++} ++ ++static void nvidia_audio_update(void *data, obs_data_t *s) ++{ ++ struct nvidia_audio_data *ng = data; ++ float intensity = (float)obs_data_get_double(s, S_NVAFX_INTENSITY); ++ const char *method = obs_data_get_string(s, S_METHOD); ++ bool vad = obs_data_get_bool(s, S_NVAFX_VAD); ++ os_atomic_set_bool(&ng->bypass_nvafx, intensity == 0.0f); ++ pthread_mutex_lock(&ng->settings_mutex); ++ if (!ng->requested_fx || strcmp(ng->requested_fx, method) != 0 || ++ ng->requested_intensity != intensity || ng->requested_vad != vad) { ++ bfree(ng->requested_fx); ++ ng->requested_fx = bstrdup(method); ++ ng->requested_intensity = intensity; ++ ng->requested_vad = vad; ++ ng->settings_generation++; ++ pthread_cond_signal(&ng->settings_changed); ++ } ++ pthread_mutex_unlock(&ng->settings_mutex); + } + + static void nvafx_logger_callback(void *data, const char *msg) +@@ -684,10 +649,11 @@ static void *nvidia_audio_create(obs_data_t *settings, obs_source_t *filter) + strcpy(ng->sdk_path, sdk_path); + ng->nvidia_sdk_dir_found = true; + ng->nvafx_initialized = false; +- ng->nvafx_loading = false; + ng->fx = NULL; + + pthread_mutex_init(&ng->nvafx_mutex, NULL); ++ pthread_mutex_init(&ng->settings_mutex, NULL); ++ pthread_cond_init(&ng->settings_changed, NULL); + + info("NVAFX SDK redist path was found here %s", sdk_path); + // set FX +@@ -702,6 +668,7 @@ static void *nvidia_audio_create(obs_data_t *settings, obs_source_t *filter) + uint32_t sample_rate = audio_output_get_sample_rate(obs_get_audio()); + size_t channels = audio_output_get_channels(obs_get_audio()); + size_t frames = (size_t)sample_rate / (1000 / BUFFER_SIZE_MSEC); ++ ng->latency = 1000000000LL / (1000 / BUFFER_SIZE_MSEC); + ng->frames = frames; + ng->channels = channels; + +@@ -741,21 +708,24 @@ static void *nvidia_audio_create(obs_data_t *settings, obs_source_t *filter) + ng->vad = 1; + + nvidia_audio_update(ng, settings); +- +- /* Setup NVIDIA logger */ +- if (nvafx_new_sdk) { +- NvAFX_Status err = NvAFX_InitializeLogger(NVAFX_LOG_LEVEL_ERROR, LOG_TARGET_CALLBACK, NULL, +- &nvafx_logger_callback, ng); +- if (err != NVAFX_STATUS_SUCCESS) { +- warn("NvAFX logger failed to initialize."); +- } ++ if (pthread_create(&ng->nvafx_thread, NULL, nvidia_audio_worker, ng) != 0) { ++ do_log(LOG_ERROR, "could not create audio configuration worker"); ++ nvidia_audio_destroy(ng); ++ return NULL; + } ++ ng->worker_started = true; + + return ng; + } + + static inline void process_fx(struct nvidia_audio_data *ng) + { ++ if (pthread_mutex_trylock(&ng->nvafx_mutex) != 0) ++ return; ++ if (!ng->use_nvafx || !ng->reinit_done || !os_atomic_load_bool(&ng->nvafx_initialized)) { ++ pthread_mutex_unlock(&ng->nvafx_mutex); ++ return; ++ } + /* Resample if necessary */ + if (ng->nvafx_resampler) { + float *output[MAX_PREPROC_CHANNELS]; +@@ -785,7 +755,6 @@ static inline void process_fx(struct nvidia_audio_data *ng) + /* Execute */ + size_t runs = ng->has_mono_src ? 1 : ng->channels; + if (ng->reinit_done) { +- pthread_mutex_lock(&ng->nvafx_mutex); + for (size_t i = 0; i < runs; i++) { + NvAFX_Status err = NvAFX_Run(ng->handle[i], &ng->nvafx_segment_buffers[i], + &ng->nvafx_segment_buffers[i], ng->num_samples_per_frame, +@@ -802,7 +771,6 @@ static inline void process_fx(struct nvidia_audio_data *ng) + } + } + } +- pthread_mutex_unlock(&ng->nvafx_mutex); + } + if (ng->has_mono_src) { + memcpy(ng->nvafx_segment_buffers[1], ng->nvafx_segment_buffers[0], NVAFX_FRAME_SIZE * sizeof(float)); +@@ -831,6 +799,7 @@ static inline void process_fx(struct nvidia_audio_data *ng) + } + } + } ++ pthread_mutex_unlock(&ng->nvafx_mutex); + } + + static inline void process(struct nvidia_audio_data *ng) +@@ -839,7 +808,7 @@ static inline void process(struct nvidia_audio_data *ng) + for (size_t i = 0; i < ng->channels; i++) + deque_pop_front(&ng->input_buffers[i], ng->copy_buffers[i], ng->frames * sizeof(float)); + +- if (ng->use_nvafx && nvidia_afx_loaded && ng->nvafx_initialized && ++ if (nvidia_afx_loaded && os_atomic_load_bool(&ng->nvafx_initialized) && + !os_atomic_load_bool(&ng->bypass_nvafx)) { + process_fx(ng); + } +-- +2.54.0.windows.1 + diff --git a/patches/README.md b/patches/README.md index b999228a..a8297ecc 100644 --- a/patches/README.md +++ b/patches/README.md @@ -1,6 +1,6 @@ # Pulsar OBS patch stack -Pulsar 3.0.0 carries **58 patches** above the pinned +Pulsar 3.0.0 carries **59 patches** above the pinned `ZabLaboratory/obs-studio` revision `bd73b922891e56839b0bc86bdc519802802f9d68`. Five foundational changes are already in that fork revision. @@ -68,6 +68,14 @@ using an atomic settings flag. It retains the filter's enabled state and does not reset/reload SDK models. Nonzero intensity remains an SDK operation and must be qualified separately, including intermediate values. +Patch 0062 recreates AFX handles on a persistent control worker when method, +intensity or VAD changes. The installed denoiser captures settings at model +load; live SetFloat alone is insufficient. Audio uses a nonblocking lock and +original PCM during loading. Destruction joins the worker before releasing +its data, outside the loader mutex. The SDK logger survives individual filters. +The installed-SDK probe covers rapid changes, destruction during loading, +multiple audio filters and optional synthetic native Program recording. + The build records the upstream pin, patch-content fingerprint and applied HEAD. It reuses only an exact clean match; `-RefreshPatches` forces replay. This preserves incremental object caches without accepting a different diff --git a/plugins/pulsar-nv-secure-load/README.md b/plugins/pulsar-nv-secure-load/README.md index 7f5a919f..cee411fc 100644 --- a/plugins/pulsar-nv-secure-load/README.md +++ b/plugins/pulsar-nv-secure-load/README.md @@ -88,9 +88,16 @@ change native pixels, bypass must restore exact pixels, and each must sustain at least 250 frames across five seconds with no skipped frames. Three audio methods must load and process; denoising/combined must reduce the native meter magnitude by at least half. Neither microphones, cameras, monitoring speakers, -recording nor network broadcast are used. The process must exit normally. +network broadcast are used. `--record-video` explicitly records the isolated +synthetic Program inside `--work`; it never records user devices or Prism's +antenna. The process must exit normally. Reports retain SDK warnings/errors separately: passing synthetic checks does not certify the absence of SDK diagnostics, portrait segmentation quality, -room-echo removal quality, scene switches, or an external native output. SDK +room-echo removal quality, or an external hardware output. Native source and +Program scene pixels, twenty switches with stacked video effects, three audio +intensity contracts, rapid updates and removal while loading are tested. +The report records render timing, SDK diagnostics and optional recording +provenance separately. Patch 0062 owns the private AFX worker lifecycle; +this header continues to own only SDK loading policy. SDK packages/models remain external machine dependencies and are never committed. diff --git a/scripts/probe-nvidia-effects.py b/scripts/probe-nvidia-effects.py index 53c1054a..404da6f8 100644 --- a/scripts/probe-nvidia-effects.py +++ b/scripts/probe-nvidia-effects.py @@ -29,7 +29,7 @@ spec.loader.exec_module(transport) KINDS = ["nv_greenscreen_filter", "nv_blur_filter", "nv_background_blur_filter", "nvidia_audiofx_filter"] -async def exercise(proc, work, report): +async def exercise(proc, work, report, record_video=False): proc.spawn() await asyncio.to_thread(proc.wait_for, transport.process.READY_RE, 45) await asyncio.to_thread(proc.wait_for_shutdown_control_ready, 10) @@ -43,6 +43,11 @@ async def exercise(proc, work, report): await ws.send(json.dumps({"op": 1, "d": identify})) assert json.loads(await ws.recv())["op"] == 2 wire = transport.Wire(ws) + report["mutedDeviceInputs"] = [] + for item in (await wire.call("GetInputList"))["inputs"]: + if item["inputKind"].startswith("wasapi_"): + await wire.call("SetInputMute", {"inputName": item["inputName"], "inputMuted": True}) + report["mutedDeviceInputs"].append(item["inputName"]) caps = await wire.vendor("GetCapabilities", vendor="pulsar") report["capabilities"] = caps inventory = {row["value"] for row in caps["capabilities"]["filters"]["values"]} @@ -64,8 +69,21 @@ async def image(source="Stripes"): await asyncio.sleep(1) baseline = await image() baseline_program = await image("NvidiaSDKRegression") + async def caption(text): + if not record_video: + return + await wire.call("SetInputSettings", {"inputName": "QualificationLabel", "inputSettings": {"text": text}, "overlay": True}) + report.setdefault("videoTimeline", []).append({"seconds": time.monotonic()-recording_started, "text": text}) + if record_video: + await wire.call("CreateInput", {"sceneName": "NvidiaSDKRegression", "inputName": "QualificationLabel", "inputKind": "text_gdiplus_v3", "inputSettings": {"text": "NVIDIA / Pulsar - qualification native\nFixture synthetique / aucune camera ni microphone", "font": {"face": "Segoe UI", "size": 36, "flags": 0}, "color": 0xffffff, "bk_color": 0x151515, "bk_opacity": 95, "outline": True}, "sceneItemEnabled": True}) + await wire.call("StartRecord") + recording_started = time.monotonic() + # Labels are part of the native recording, but the pixel oracle + # below remains the unchanged source, independent of those labels. + baseline_program = await image("NvidiaSDKRegression") report["video"] = [] for kind in KINDS[:3]: + await caption("NVIDIA / Pulsar - sortie native 1080p60\n"+kind+" - traitement actif (fixture synthetique)") settings = {"mode": 1, "threshold": .8, "processing_interval": 1} if kind == KINDS[0] else {"intensity": .8} await wire.call("CreateSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/test", "filterKind": kind, "filterSettings": settings}) actual = await wire.call("GetSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/test"}) @@ -78,7 +96,8 @@ async def image(source="Stripes"): samples.append(await wire.call("GetStats")) treated = await image() row = {"kind": kind, "settings": actual["filterSettings"], "pixelDifferenceMean": float(np.abs(treated.astype(float)-baseline).mean()), "alphaRange": [int(treated[:,:,3].min()), int(treated[:,:,3].max())], "frameDelta": samples[-1]["renderTotalFrames"]-before["renderTotalFrames"], "skippedDelta": samples[-1]["renderSkippedFrames"]-before["renderSkippedFrames"], "meanRenderMs": sum(s["averageFrameRenderTime"] for s in samples)/len(samples)} - row["programScenePixelDifferenceMean"] = float(np.abs((await image("NvidiaSDKRegression")).astype(float)-baseline_program).mean()) + # Exclude the optional native caption from the Program oracle. + row["programScenePixelDifferenceMean"] = float(np.abs((await image("NvidiaSDKRegression"))[40:].astype(float)-baseline_program[40:]).mean()) report["video"].append(row) assert row["pixelDifferenceMean"] > 1, row assert row["programScenePixelDifferenceMean"] > 1, row @@ -100,6 +119,8 @@ async def image(source="Stripes"): # or inactive-scene cache is involved in this isolated native check. await wire.call("CreateScene", {"sceneName": "NvidiaSDKRegressionB"}) await wire.call("CreateSceneItem", {"sceneName": "NvidiaSDKRegressionB", "sourceName": "Stripes", "sceneItemEnabled": True}) + if record_video: + await wire.call("CreateSceneItem", {"sceneName": "NvidiaSDKRegressionB", "sourceName": "QualificationLabel", "sceneItemEnabled": True}) for kind in KINDS[1:3]: await wire.call("CreateSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/"+kind, "filterKind": kind, "filterSettings": {"intensity": .4}}) await asyncio.sleep(2) @@ -108,6 +129,7 @@ async def image(source="Stripes"): for index in range(20): scene = "NvidiaSDKRegressionB" if index % 2 == 0 else "NvidiaSDKRegression" await wire.call("SetCurrentProgramScene", {"sceneName": scene}) + await caption(f"NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif {index+1}/20 : {scene}") await asyncio.sleep(.2) assert (await wire.call("GetCurrentProgramScene"))["currentProgramSceneName"] == scene report["stackedSceneSwitches"].append({"scene": scene, "pixelDifferenceMean": float(np.abs((await image()).astype(float)-baseline).mean())}) @@ -117,6 +139,7 @@ async def image(source="Stripes"): assert report["stackedStats"]["frameDelta"] >= 240 and report["stackedStats"]["skippedDelta"] == 0 for kind in KINDS[1:3]: await wire.call("RemoveSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/"+kind}) + await wire.call("SetCurrentProgramScene", {"sceneName": "NvidiaSDKRegression"}) rng = np.random.default_rng(20261010) noise = (rng.normal(0, .08, 48000*12).clip(-1, 1)*32767).astype(" intermediate[0]["magnitude"]*1.2 report["audio"]["methods"][-1]["intermediateIntensityPassed"] = intermediate_passed if not intermediate_passed: live_intensity_failures.append(method+"/intermediate") + await caption(f"NVIDIA AFX / {method} - demande 0 %\nBypass PCM / mesure native en cours") await wire.call("SetSourceFilterSettings", {"sourceName": "Noise", "filterName": "sdk/audio", "filterSettings": {"intensity": 0.0}, "overlay": True}) await asyncio.sleep(1) bypass_magnitude = await levels(2) + await caption(f"NVIDIA AFX / {method} - intensite 0 %\nBypass PCM : {bypass_magnitude:.5f} / original : {original:.5f}") report["audio"]["methods"][-1]["liveIntensityZeroMagnitude"] = bypass_magnitude # A contemporaneous disabled-filter control distinguishes an SDK # slider failure from a stopped fixture or source-loop gap. + await caption(f"NVIDIA AFX / {method}\nFiltre desactive : controle du signal original") await wire.call("SetSourceFilterEnabled", {"sourceName": "Noise", "filterName": "sdk/audio", "filterEnabled": False}) await asyncio.sleep(.5) disabled = await levels(2) report["audio"]["methods"][-1]["disabledMagnitude"] = disabled assert disabled > original*.8, ("fixture no longer running", original, disabled) + await caption(f"NVIDIA AFX / {method} - reappliquer 100 %\nChargement asynchrone puis mesure native du signal") await wire.call("SetSourceFilterSettings", {"sourceName": "Noise", "filterName": "sdk/audio", "filterSettings": {"intensity": 1.0}, "overlay": True}) await wire.call("SetSourceFilterEnabled", {"sourceName": "Noise", "filterName": "sdk/audio", "filterEnabled": True}) await asyncio.sleep(1) @@ -179,6 +210,60 @@ async def levels(seconds): if not intensity_passed: live_intensity_failures.append(method) await wire.call("RemoveSourceFilter", {"sourceName": "Noise", "filterName": "sdk/audio"}) + print(f"AFX {method}: intermediate={intermediate_passed}, zero={intensity_passed}", flush=True) + # Rapid settings changes, real VAD reload, and removal while loading. + # Every request has a bounded wire timeout. This is also a regression + # for the former join-under-loader-mutex destruction deadlock. + await caption("NVIDIA AFX - test de cycle de vie\n72 reglages rapides et 6 retraits pendant chargement") + report["audioLifecycle"] = [] + for cycle in range(6): + await wire.call("CreateSourceFilter", {"sourceName": "Noise", "filterName": "sdk/stress", "filterKind": KINDS[3], "filterSettings": {"method": "denoiser", "intensity": 1.0, "vad": False}}) + durations = [] + for index in range(12): + started = time.perf_counter() + await wire.call("SetSourceFilterSettings", {"sourceName": "Noise", "filterName": "sdk/stress", "filterSettings": {"method": ["denoiser", "dereverb", "dereverb_denoiser"][index % 3], "intensity": .25 if index % 2 else .75, "vad": bool(index % 2)}, "overlay": True}) + durations.append(time.perf_counter()-started) + # Allow native deferred settings/worker execution before detaching. + await asyncio.sleep(.05) + started = time.perf_counter() + await wire.call("RemoveSourceFilter", {"sourceName": "Noise", "filterName": "sdk/stress"}) + row = {"cycle": cycle, "updates": len(durations), "maxUpdateMs": max(durations)*1000, "removeMs": (time.perf_counter()-started)*1000} + report["audioLifecycle"].append(row) + assert row["maxUpdateMs"] < 1000 and row["removeMs"] < 5000, row + # Two active filters must not tear down each other's SDK logger/state. + await caption("NVIDIA AFX - deux filtres audio\nRetrait du premier / le second doit rester operationnel") + for name in ["sdk/first", "sdk/second"]: + await wire.call("CreateSourceFilter", {"sourceName": "Noise", "filterName": name, "filterKind": KINDS[3], "filterSettings": {"method": "denoiser", "intensity": .5, "vad": False}}) + await asyncio.sleep(4) + await wire.call("RemoveSourceFilter", {"sourceName": "Noise", "filterName": "sdk/first"}) + await asyncio.sleep(2) + surviving = await levels(2) + assert surviving < original*.8, surviving + report["audioSurvivingFilterMagnitude"] = surviving + await caption("NVIDIA AFX - VAD et continuite\nIntensite 25 % / VAD ON puis OFF / qualite voix non evaluee") + await wire.call("SetSourceFilterSettings", {"sourceName": "Noise", "filterName": "sdk/second", "filterSettings": {"intensity": .25, "vad": True}, "overlay": True}) + await asyncio.sleep(2) + vad_on = await levels(2) + vad_on_settings = (await wire.call("GetSourceFilter", {"sourceName": "Noise", "filterName": "sdk/second"}))["filterSettings"] + await wire.call("SetSourceFilterSettings", {"sourceName": "Noise", "filterName": "sdk/second", "filterSettings": {"vad": False}, "overlay": True}) + await asyncio.sleep(2) + vad_off = await levels(2) + vad_off_settings = (await wire.call("GetSourceFilter", {"sourceName": "Noise", "filterName": "sdk/second"}))["filterSettings"] + report["audioLiveVad"] = {"enabledMagnitude": vad_on, "disabledMagnitude": vad_off, "enabledSettings": vad_on_settings, "disabledSettings": vad_off_settings, "scope": "live reload/readback and signal continuity; white noise cannot qualify voice detection accuracy"} + assert vad_on_settings["vad"] is True and vad_off_settings["vad"] is False + # At .25 the SDK retains a dry component even with VAD; requiring + # silence would incorrectly test full suppression at a partial ratio. + assert vad_on > original*.5 and vad_off > original*.5, report["audioLiveVad"] + await caption("NVIDIA AFX - bypass a 0 % avec VAD ON\nLe signal original doit continuer") + await wire.call("SetSourceFilterSettings", {"sourceName": "Noise", "filterName": "sdk/second", "filterSettings": {"intensity": 0., "vad": True}, "overlay": True}) + await asyncio.sleep(1) + report["audioLiveVad"]["zeroWithVadMagnitude"] = await levels(2) + assert report["audioLiveVad"]["zeroWithVadMagnitude"] > original*.8 + await wire.call("RemoveSourceFilter", {"sourceName": "Noise", "filterName": "sdk/second"}) + await caption("NVIDIA / Pulsar - fin de qualification\n72 mises a jour rapides / retrait pendant chargement / deux filtres audio") + if record_video: + await asyncio.sleep(2) + report["nativeRecording"] = (await wire.call("StopRecord"))["outputPath"] await wire.call("RemoveInput", {"inputName": "Noise"}) assert (await wire.call("GetSourceFilterList", {"sourceName": "Stripes"}))["filters"] == [] report["checks"] = ["SDK usable/version", "four filters registered", "three VFX source and Program scene pixel changes and exact bypass", "live VFX intensity", "20 scene switches with stacked VFX", "native render timings", "three AFX model methods", "noise suppression signal", "filter removal"] @@ -189,6 +274,7 @@ def main(): parser = argparse.ArgumentParser(description=__doc__) for key in ["exe", "work", "report", "afx", "vfx"]: parser.add_argument("--"+key, type=Path, required=True) + parser.add_argument("--record-video", action="store_true", help="Record only this isolated synthetic Program, inside --work") args = parser.parse_args() work = args.work.resolve() work.mkdir(parents=True, exist_ok=True) @@ -200,7 +286,7 @@ def main(): nv_module = args.exe.resolve().parents[2] / "obs-plugins/64bit/nv-filters.dll" report = {"started": time.time(), "mode": "isolated installed NVIDIA SDK synthetic regression", "exe": str(args.exe.resolve()), "exeSha256": hashlib.sha256(args.exe.read_bytes()).hexdigest(), "nvFiltersSha256": hashlib.sha256(nv_module.read_bytes()).hexdigest(), "sdkPaths": {"afx": str(args.afx.resolve()), "vfx": str(args.vfx.resolve())}} try: - asyncio.run(exercise(proc, work, report)) + asyncio.run(exercise(proc, work, report, args.record_video)) except Exception as error: report["failure"] = {"type": type(error).__name__, "message": str(error)} finally: From 32d3830a0f18aa25243548a649bbbd84c02786f1 Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Sat, 10 Oct 2026 13:18:46 +0200 Subject: [PATCH 14/17] test(nvidia): qualify native video and decoded effect audio --- .../eleven/20261010T111700Z-media-check.json | 69 + .../eleven/20261010T111700Z-media-probe.txt | 26 + .../eleven/20261010T111700Z-native-video.json | 1136 +++++++++++++++++ .../eleven/20261010T111700Z-report.md | 39 + .../20261010T111700Z-video-manifest.json | 69 + scripts/probe-nvidia-effects.py | 2 +- 6 files changed, 1340 insertions(+), 1 deletion(-) create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-media-check.json create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-media-probe.txt create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-native-video.json create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-report.md create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-video-manifest.json diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-media-check.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-media-check.json new file mode 100644 index 00000000..1a99675f --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-media-check.json @@ -0,0 +1,69 @@ +{ + "passed": true, + "videoSha256": "ecec251eed0ab5bd9542871ab5ed79d4c28ef455d3a4e59a8510f618036369ec", + "check": "decoded H264 pixels and decoded AAC signal follow actual native effect/settings steps; not just metadata", + "audio": [ + { + "method": "denoiser", + "encodedAudioRms": { + "75": 0.012089559808373451, + "25": 0.03981930390000343, + "0": 0.06584518402814865 + } + }, + { + "method": "dereverb", + "encodedAudioRms": { + "75": 0.01606990024447441, + "25": 0.03631529584527016, + "0": 0.04579981788992882 + } + }, + { + "method": "dereverb_denoiser", + "encodedAudioRms": { + "75": 0.01224804762750864, + "25": 0.03608640655875206, + "0": 0.047915466129779816 + } + } + ], + "videoSamples": [ + { + "seconds": 5, + "decodedPixelSha256": "2756c96258658520b1972766c7a9ec2b7e70cdf70991c19606e4221bc02a4c0a", + "bodyMean": 0.0, + "bodyStd": 0.0 + }, + { + "seconds": 14, + "decodedPixelSha256": "8415dc460e9f3d441a905443160417fb6806434396163121c4aa73968f1f1b0c", + "bodyMean": 73.025, + "bodyStd": 0.22220486043288973 + }, + { + "seconds": 25, + "decodedPixelSha256": "276c3b1e10aa8e5cc416d5192cee0b6cf163d435218e4412427064d7ab0a99c6", + "bodyMean": 73.025, + "bodyStd": 0.22220486043288973 + }, + { + "seconds": 36, + "decodedPixelSha256": "4c10def1cfc944d48f60c7dff1675a62ffa42979c5e883ce483380e80d05ebb0", + "bodyMean": 73.034375, + "bodyStd": 0.288432590694949 + }, + { + "seconds": 52, + "decodedPixelSha256": "7bfe468fc649198c5c7b894cca63507d3161ac76b2a4b6d4c351747a6907792c", + "bodyMean": 72.98439732142857, + "bodyStd": 3.4364514430888016 + }, + { + "seconds": 68, + "decodedPixelSha256": "53f448e6a50826e3d9e62db71ac20857aa75155d1da72e04366697b90ab14964", + "bodyMean": 72.98439732142857, + "bodyStd": 3.4364514430888016 + } + ] +} diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-media-probe.txt b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-media-probe.txt new file mode 100644 index 00000000..599d5782 --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-media-probe.txt @@ -0,0 +1,26 @@ +import json,subprocess,hashlib +from pathlib import Path +import numpy as np +root=Path(__file__).parents[2] +report=json.loads((root/'.cache/nvidia-proof-captioned/result.json').read_text()) +video=Path('D:/Documents/Zab/Artifacts/2026-10-10/nvidia-prism-proof/Pulsar-nvidia-native.mp4') +pcm=np.frombuffer(subprocess.check_output(['ffmpeg','-v','error','-i',str(video),'-vn','-f','f32le','-ac','1','-ar','48000','pipe:1']),np.float32) +rows=[] +for method in ['denoiser','dereverb','dereverb_denoiser']: + levels={} + for ratio in [75,25,0]: + entry=next(r for r in report['videoTimeline'] if r['text'].startswith(f'NVIDIA AFX / {method} - demande {ratio}%') or r['text'].startswith(f'NVIDIA AFX / {method} - demande {ratio} %')) + start=round((entry['seconds']+1.25)*48000);samples=pcm[start:start+48000] + assert len(samples)==48000 + levels[str(ratio)]=float(np.sqrt(np.mean(samples**2))) + assert levels['25']>levels['75']*1.2 and levels['0']>levels['25']*1.1,(method,levels) + rows.append({'method':method,'encodedAudioRms':levels}) +samples=[] +for seconds in [5,14,25,36,52,68]: + rgb=subprocess.check_output(['ffmpeg','-v','error','-ss',str(seconds),'-i',str(video),'-frames:v','1','-vf','scale=320:180','-pix_fmt','rgb24','-f','rawvideo','pipe:1']) + array=np.frombuffer(rgb,np.uint8).reshape(180,320,3) + samples.append({'seconds':seconds,'decodedPixelSha256':hashlib.sha256(rgb).hexdigest(),'bodyMean':float(array[40:].mean()),'bodyStd':float(array[40:].std())}) +assert samples[0]['bodyMean']<5 and samples[1]['bodyMean']>40 +result={'passed':True,'videoSha256':hashlib.sha256(video.read_bytes()).hexdigest(),'check':'decoded H264 pixels and decoded AAC signal follow actual native effect/settings steps; not just metadata','audio':rows,'videoSamples':samples} +(root/'.cache/nvidia-proof-captioned/media-check.json').write_text(json.dumps(result,indent=2)+'\n') +print(json.dumps(result,indent=2)) diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-native-video.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-native-video.json new file mode 100644 index 00000000..7094d51f --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-native-video.json @@ -0,0 +1,1136 @@ +{ + "started": 1791630745.8394473, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "nvFiltersSha256": "e52040f52b9510ec34afd3e142df276b0252745f8713b655b8943d27f40b1365", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "mutedDeviceInputs": [ + "PulsarDesktopAudio" + ], + "capabilities": { + "active_encoder": "nvenc", + "audio_bitrate": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ], + "capabilities": { + "active_encoder": { + "applicability": "boot-fixed", + "value": "nvenc" + }, + "audio_bitrate": { + "applicability": "live", + "max": 512, + "min": 64, + "step": 32, + "values": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ] + }, + "audio_monitoring": { + "applicability": "live", + "available": true, + "device_bound": true, + "device_id": "{0.0.0.00000000}.{3a9b3ef5-04ae-4b95-88d7-53e24294bc68}", + "device_name": "Default", + "device_selectable": true + }, + "audio_sample_rate": { + "applicability": "read-only", + "hz": 48000 + }, + "audio_speaker_layout": { + "applicability": "read-only", + "channels": 2, + "layout": "stereo" + }, + "audio_tracks": { + "applicability": "read-only", + "bound": 1, + "count": 6, + "tracks": [ + { + "value": 1 + } + ] + }, + "destination_kinds": { + "applicability": "live", + "values": [ + { + "value": "rtmp_custom" + }, + { + "value": "vod_local" + }, + { + "value": "twitch" + }, + { + "value": "youtube" + } + ] + }, + "encoder_families": { + "applicability": "boot-fixed", + "values": [ + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 20, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "ultrafast" + }, + { + "value": "superfast" + }, + { + "value": "veryfast" + }, + { + "value": "faster" + }, + { + "value": "fast" + }, + { + "value": "medium" + }, + { + "value": "slow" + }, + { + "value": "slower" + }, + { + "value": "veryslow" + }, + { + "value": "placebo" + } + ], + "profiles": [ + { + "value": "baseline" + }, + { + "value": "main" + }, + { + "value": "high" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "VBR" + } + ], + "value": "x264" + }, + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 10, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "p1" + }, + { + "value": "p2" + }, + { + "value": "p3" + }, + { + "value": "p4" + }, + { + "value": "p5" + }, + { + "value": "p6" + }, + { + "value": "p7" + } + ], + "profiles": [ + { + "value": "high" + }, + { + "value": "main" + }, + { + "value": "baseline" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "CQP" + }, + { + "value": "VBR" + } + ], + "value": "nvenc" + } + ] + }, + "encoders": { + "applicability": "boot-fixed", + "values": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ] + }, + "filters": { + "applicability": "live", + "values": [ + { + "value": "nvidia_audiofx_filter" + }, + { + "value": "nv_greenscreen_filter" + }, + { + "value": "nv_blur_filter" + }, + { + "value": "nv_background_blur_filter" + }, + { + "value": "mask_filter" + }, + { + "value": "mask_filter_v2" + }, + { + "value": "crop_filter" + }, + { + "value": "gain_filter" + }, + { + "value": "basic_eq_filter" + }, + { + "value": "hdr_tonemap_filter" + }, + { + "value": "color_filter" + }, + { + "value": "color_filter_v2" + }, + { + "value": "scale_filter" + }, + { + "value": "scroll_filter" + }, + { + "value": "gpu_delay" + }, + { + "value": "color_key_filter" + }, + { + "value": "color_key_filter_v2" + }, + { + "value": "clut_filter" + }, + { + "value": "sharpness_filter" + }, + { + "value": "sharpness_filter_v2" + }, + { + "value": "chroma_key_filter" + }, + { + "value": "chroma_key_filter_v2" + }, + { + "value": "async_delay_filter" + }, + { + "value": "noise_suppress_filter" + }, + { + "value": "noise_suppress_filter_v2" + }, + { + "value": "invert_polarity_filter" + }, + { + "value": "noise_gate_filter" + }, + { + "value": "compressor_filter" + }, + { + "value": "limiter_filter" + }, + { + "value": "expander_filter" + }, + { + "value": "upward_compressor_filter" + }, + { + "value": "luma_key_filter" + }, + { + "value": "luma_key_filter_v2" + }, + { + "value": "pulsar_vignette_filter" + } + ] + }, + "graphics_adapters": { + "active_index": 0, + "applicability": "read-only", + "values": [ + { + "index": 0, + "value": "NVIDIA GeForce RTX 3070" + } + ] + }, + "nv_filters": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "module_loaded": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "output_scales": { + "applicability": "boot-fixed", + "canvas": { + "height": 1080, + "width": 1920 + }, + "values": [ + { + "height": 1080, + "scale": 1.0, + "value": "1920x1080", + "width": 1920 + } + ] + }, + "record_container": { + "applicability": "boot-fixed", + "value": "mp4", + "values": [ + { + "value": "mp4" + }, + { + "value": "mkv" + } + ] + }, + "record_markers": { + "add_chapter": false, + "applicability": "read-only", + "split_file": true + }, + "source_kinds": { + "applicability": "live", + "values": [ + { + "value": "audio_line" + }, + { + "value": "pulsar_preview_frame" + }, + { + "value": "image_source" + }, + { + "value": "color_source" + }, + { + "value": "color_source_v2" + }, + { + "value": "color_source_v3" + }, + { + "value": "slideshow" + }, + { + "value": "slideshow_v2" + }, + { + "value": "ffmpeg_source" + }, + { + "value": "text_gdiplus" + }, + { + "value": "text_gdiplus_v2" + }, + { + "value": "text_gdiplus_v3" + }, + { + "value": "browser_source" + }, + { + "value": "text_ft2_source" + }, + { + "value": "text_ft2_source_v2" + }, + { + "value": "vlc_source" + }, + { + "value": "monitor_capture" + }, + { + "value": "window_capture" + }, + { + "value": "game_capture" + }, + { + "value": "dshow_input" + }, + { + "value": "wasapi_input_capture" + }, + { + "value": "wasapi_output_capture" + }, + { + "value": "wasapi_process_output_capture" + } + ] + }, + "video_bitrate": { + "applicability": "live", + "max": 50000, + "min": 200, + "step": 50 + }, + "video_colorimetry": { + "applicability": "read-only", + "format": "NV12", + "range": "Partial", + "value": "709" + } + }, + "encoders": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ], + "version": 1, + "video_bitrate": { + "max": 50000, + "min": 200 + } + }, + "video": [ + { + "kind": "nv_greenscreen_filter", + "settings": { + "mode": 1, + "processing_interval": 1, + "threshold": 0.8 + }, + "pixelDifferenceMean": 137.625, + "alphaRange": [ + 0, + 0 + ], + "frameDelta": 302, + "skippedDelta": 0, + "meanRenderMs": 4.3996648, + "programScenePixelDifferenceMean": 137.625 + }, + { + "kind": "nv_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 301, + "skippedDelta": 0, + "meanRenderMs": 1.7819302000000001, + "programScenePixelDifferenceMean": 58.8734375, + "liveIntensity": [ + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.2, + "pixelDifferenceMean": 58.86875 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + }, + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + } + ] + }, + { + "kind": "nv_background_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 303, + "skippedDelta": 0, + "meanRenderMs": 5.187521, + "programScenePixelDifferenceMean": 58.8734375, + "liveIntensity": [ + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.2, + "pixelDifferenceMean": 58.86875 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + }, + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + } + ] + } + ], + "videoTimeline": [ + { + "seconds": 0.0, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_greenscreen_filter - activation puis mesure (fixture synthetique)" + }, + { + "seconds": 8.764999999999418, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_blur_filter - activation puis mesure (fixture synthetique)" + }, + { + "seconds": 20.436999999990803, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_background_blur_filter - activation puis mesure (fixture synthetique)" + }, + { + "seconds": 34.21899999999732, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 1/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 34.4369999999908, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 2/20 : NvidiaSDKRegression" + }, + { + "seconds": 34.65600000000268, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 3/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 34.85899999999674, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 4/20 : NvidiaSDKRegression" + }, + { + "seconds": 35.0619999999908, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 5/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 35.28100000000268, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 6/20 : NvidiaSDKRegression" + }, + { + "seconds": 35.51499999999942, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 7/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 35.71899999999732, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 8/20 : NvidiaSDKRegression" + }, + { + "seconds": 35.921999999991385, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 9/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 36.125, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 10/20 : NvidiaSDKRegression" + }, + { + "seconds": 36.34399999999732, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 11/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 36.5619999999908, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 12/20 : NvidiaSDKRegression" + }, + { + "seconds": 36.78100000000268, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 13/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 37.0, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 14/20 : NvidiaSDKRegression" + }, + { + "seconds": 37.1869999999908, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 15/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 37.38999999999942, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 16/20 : NvidiaSDKRegression" + }, + { + "seconds": 37.60899999999674, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 17/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 37.84399999999732, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 18/20 : NvidiaSDKRegression" + }, + { + "seconds": 38.0619999999908, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 19/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 38.26499999999942, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 20/20 : NvidiaSDKRegression" + }, + { + "seconds": 41.63999999999942, + "text": "NVIDIA / Pulsar - audio synthetique 48 kHz\nSignal original : magnitude native 0.07980" + }, + { + "seconds": 41.63999999999942, + "text": "NVIDIA AFX / denoiser\nIntensite 100 % / VAD OFF - bruit synthetique, aucune voix" + }, + { + "seconds": 46.65600000000268, + "text": "NVIDIA AFX / denoiser - demande 75%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 49.65600000000268, + "text": "NVIDIA AFX / denoiser - intensite 75%\nMagnitude native : 0.02371 / original : 0.07980" + }, + { + "seconds": 49.65600000000268, + "text": "NVIDIA AFX / denoiser - demande 25%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 52.78100000000268, + "text": "NVIDIA AFX / denoiser - intensite 25%\nMagnitude native : 0.05854 / original : 0.07980" + }, + { + "seconds": 52.78100000000268, + "text": "NVIDIA AFX / denoiser - demande 0 %\nBypass PCM / mesure native en cours" + }, + { + "seconds": 55.78100000000268, + "text": "NVIDIA AFX / denoiser - intensite 0 %\nBypass PCM : 0.07900 / original : 0.07980" + }, + { + "seconds": 55.78100000000268, + "text": "NVIDIA AFX / denoiser\nFiltre desactive : controle du signal original" + }, + { + "seconds": 58.421999999991385, + "text": "NVIDIA AFX / denoiser - reappliquer 100 %\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 61.5619999999908, + "text": "NVIDIA AFX / dereverb\nIntensite 100 % / VAD OFF - bruit synthetique, aucune voix" + }, + { + "seconds": 66.57799999999406, + "text": "NVIDIA AFX / dereverb - demande 75%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 69.57799999999406, + "text": "NVIDIA AFX / dereverb - intensite 75%\nMagnitude native : 0.03374 / original : 0.07980" + }, + { + "seconds": 69.57799999999406, + "text": "NVIDIA AFX / dereverb - demande 25%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 72.71899999999732, + "text": "NVIDIA AFX / dereverb - intensite 25%\nMagnitude native : 0.06228 / original : 0.07980" + }, + { + "seconds": 72.71899999999732, + "text": "NVIDIA AFX / dereverb - demande 0 %\nBypass PCM / mesure native en cours" + }, + { + "seconds": 75.84399999999732, + "text": "NVIDIA AFX / dereverb - intensite 0 %\nBypass PCM : 0.07909 / original : 0.07980" + }, + { + "seconds": 75.84399999999732, + "text": "NVIDIA AFX / dereverb\nFiltre desactive : controle du signal original" + }, + { + "seconds": 78.5, + "text": "NVIDIA AFX / dereverb - reappliquer 100 %\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 81.63999999999942, + "text": "NVIDIA AFX / dereverb_denoiser\nIntensite 100 % / VAD OFF - bruit synthetique, aucune voix" + }, + { + "seconds": 86.67199999999139, + "text": "NVIDIA AFX / dereverb_denoiser - demande 75%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 89.8119999999908, + "text": "NVIDIA AFX / dereverb_denoiser - intensite 75%\nMagnitude native : 0.02523 / original : 0.07980" + }, + { + "seconds": 89.8119999999908, + "text": "NVIDIA AFX / dereverb_denoiser - demande 25%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 92.95299999999406, + "text": "NVIDIA AFX / dereverb_denoiser - intensite 25%\nMagnitude native : 0.05928 / original : 0.07980" + }, + { + "seconds": 92.95299999999406, + "text": "NVIDIA AFX / dereverb_denoiser - demande 0 %\nBypass PCM / mesure native en cours" + }, + { + "seconds": 96.09399999999732, + "text": "NVIDIA AFX / dereverb_denoiser - intensite 0 %\nBypass PCM : 0.07899 / original : 0.07980" + }, + { + "seconds": 96.09399999999732, + "text": "NVIDIA AFX / dereverb_denoiser\nFiltre desactive : controle du signal original" + }, + { + "seconds": 98.71899999999732, + "text": "NVIDIA AFX / dereverb_denoiser - reappliquer 100 %\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 101.875, + "text": "NVIDIA AFX - test de cycle de vie\n72 reglages rapides et 6 retraits pendant chargement" + }, + { + "seconds": 102.23399999999674, + "text": "NVIDIA AFX - deux filtres audio\nRetrait du premier / le second doit rester operationnel" + }, + { + "seconds": 110.25, + "text": "NVIDIA AFX - VAD et continuite\nIntensite 25 % / VAD ON puis OFF / qualite voix non evaluee" + }, + { + "seconds": 118.40600000000268, + "text": "NVIDIA AFX - bypass a 0 % avec VAD ON\nLe signal original doit continuer" + }, + { + "seconds": 121.5619999999908, + "text": "NVIDIA / Pulsar - fin de qualification\n72 mises a jour rapides / retrait pendant chargement / deux filtres audio" + } + ], + "stackedSceneSwitches": [ + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + } + ], + "stackedStats": { + "frameDelta": 255, + "skippedDelta": 0, + "lastRenderMs": 6.23608 + }, + "audio": { + "fixture": "seeded white noise, raw native volume-meter magnitude", + "baseline": 0.07980244570091123, + "methods": [ + { + "method": "denoiser", + "magnitude": 0.007318857026522628, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.023706081323325634 + }, + { + "intensity": 0.25, + "magnitude": 0.05854214795055936 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.07900163605809211, + "disabledMagnitude": 0.07985079288482666, + "liveIntensityOneMagnitude": 0.006577236283327298, + "liveIntensityPassed": true + }, + { + "method": "dereverb", + "magnitude": 0.03687059510177319, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.03374231458341671 + }, + { + "intensity": 0.25, + "magnitude": 0.06228198263733113 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.07909104432309827, + "disabledMagnitude": 0.07996813404672551, + "liveIntensityOneMagnitude": 0.02695555974286599, + "liveIntensityPassed": true + }, + { + "method": "dereverb_denoiser", + "magnitude": 0.00429029626636833, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.0252332613472977 + }, + { + "intensity": 0.25, + "magnitude": 0.05928376091064 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.07898673607457068, + "disabledMagnitude": 0.08011310576246335, + "liveIntensityOneMagnitude": 0.014063334671359572, + "liveIntensityPassed": true + } + ], + "liveIntensityPassed": true + }, + "audioLifecycle": [ + { + "cycle": 0, + "updates": 12, + "maxUpdateMs": 0.5402999959187582, + "removeMs": 1.1962999997194856 + }, + { + "cycle": 1, + "updates": 12, + "maxUpdateMs": 0.3147999959765002, + "removeMs": 1.4398999919649214 + }, + { + "cycle": 2, + "updates": 12, + "maxUpdateMs": 0.27550000231713057, + "removeMs": 1.3953000016044825 + }, + { + "cycle": 3, + "updates": 12, + "maxUpdateMs": 0.9741999965626746, + "removeMs": 1.2672000011662021 + }, + { + "cycle": 4, + "updates": 12, + "maxUpdateMs": 0.4411999980220571, + "removeMs": 1.1152000079164281 + }, + { + "cycle": 5, + "updates": 12, + "maxUpdateMs": 0.6311000033747405, + "removeMs": 1.2232999870320782 + } + ], + "audioSurvivingFilterMagnitude": 0.03915121234380282, + "audioLiveVad": { + "enabledMagnitude": 0.059079797495348176, + "disabledMagnitude": 0.060202591903047796, + "enabledSettings": { + "intensity": 0.25, + "method": "denoiser", + "vad": true + }, + "disabledSettings": { + "intensity": 0.25, + "method": "denoiser", + "vad": false + }, + "scope": "live reload/readback and signal continuity; white noise cannot qualify voice detection accuracy", + "zeroWithVadMagnitude": 0.07916499875367634 + }, + "nativeRecording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-proof-captioned\\pulsar-20261010-131228.mp4", + "checks": [ + "SDK usable/version", + "four filters registered", + "three VFX source and Program scene pixel changes and exact bypass", + "live VFX intensity", + "20 scene switches with stacked VFX", + "native render timings", + "three AFX model methods", + "noise suppression signal", + "filter removal" + ], + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T11:12:25Z INFO 1n82XTCHuU57VQL5 libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T11:12:25Z INFO 1n82XTCHuU57VQL5 libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T11:12:26Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T11:12:26Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: | enabled", + "2026-10-10T11:12:26Z INFO 1n82XTCHuU57VQL5 NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T11:12:26Z INFO 1n82XTCHuU57VQL5 NVIDIA VIDEO FX | : enabled, redistributable found", + "2026-10-10T11:13:10Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:13:30Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:13:50Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:14:10Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:14:10Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:14:10Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:14:10Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:14:10Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:14:10Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:14:10Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/first' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T11:14:10Z INFO 1n82XTCHuU57VQL5 NVIDIA Audio Effects: 'sdk/second' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK" + ], + "sdkDiagnostics": [], + "qualification": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity", + "passed": true +} diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-report.md b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-report.md new file mode 100644 index 00000000..4db5433e --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-report.md @@ -0,0 +1,39 @@ +# Qualification du worker NVIDIA et vidéo native + +La correction AFX crée des handles actualisés sur un worker persistant pour +méthode/intensité/VAD. L'audio conserve le PCM original pendant le chargement ; +le callback ne bloque pas sur ce chargement. Le retrait rejoint le worker avant +de libérer ses données, hors de son mutex. Le logger global vit jusqu'au +déchargement du SDK. Patch 0062, source signée +`6fc5905315af589cde8dc1f7c85212d5ee6ad65a`. + +La sonde finale `20261010T111700Z-native-video.json` passe : trois VFX, +bypass exact, 20 switches natifs, trois méthodes AFX aux intensités +0/25/75/100 %, 72 changements rapides, six retraits pendant chargement, +deux filtres audio et continuité du VAD. Aucun diagnostic SDK, arrêt code 0 +non forcé. `20261010T111700Z-media-check.json` décode réellement H.264/AAC +et confirme les pixels traités et la variation du signal audio enregistré. + +Candidate et DLL intégrée au dev commun : SHA256 +`e52040f52b9510ec34afd3e142df276b0252745f8713b655b8943d27f40b1365`. +Seule cette DLL a changé ; rollback conservé. Les autres DLL, CEF et Solar +sont préservés. Replay des patches NVIDIA sur le pin, build MSVC x64 du +module, compilation Python et contrôle packaging passent. + +La vidéo native dure 123,57 s, 1920×1080, 60 fps, H.264 + AAC 48 kHz stéréo. +Elle utilise uniquement motifs et bruit synthétiques, l'entrée bureau étant +explicitement muette. Le manifest associé enregistre les hashes du fichier +original et du livrable avec faststart. Les deux scènes de test partagent le +contenu traité ; ce test natif ne représente pas un Take Orion utilisateur. + +[Vidéo native](D:/Documents/Zab/Artifacts/2026-10-10/nvidia-prism-proof/Pulsar-nvidia-native.mp4). +[Rapport utilisateur et matrice des 27 capacités](D:/Documents/Zab/Artifacts/2026-10-10/nvidia-prism-proof/Rapport-NVIDIA-Prism.md). +L'agrégat versionné appartient à Prism : +`evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-report.md`. + +Cette qualification locale ne certifie pas la qualité de voix réelle, le +détourage esthétique, l'écho d'une pièce, une sortie physique, une session +longue ou la capacité maximale. Le build complet release/CI est distinct. +Auto Frame, Eye Contact et les autres consommateurs absents ne deviennent +pas disponibles par l'installation d'AFX/VFX. Un essai intermédiaire sous +capture UI concurrente a mesuré deux frames sautées et n'est pas promu. diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-video-manifest.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-video-manifest.json new file mode 100644 index 00000000..b28f7c17 --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-video-manifest.json @@ -0,0 +1,69 @@ +{ + "nativeDllSha256": "e52040f52b9510ec34afd3e142df276b0252745f8713b655b8943d27f40b1365", + "sourceCandidate": "6fc5905315af589cde8dc1f7c85212d5ee6ad65a", + "nativeRecordingOriginalSha256": "b2a6f59edcafc5a520e8d094aae69ac1b463b567879ba1d232275712d7ed776d", + "videos": { + "Prism-preview-nvidia.mp4": { + "sha256": "0a75b1fc840cd362a9dff74f68349f2b0a317e807e23b5fa34200d45c20bb08c", + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "index": 0, + "codec_name": "h264", + "width": 1440, + "height": 900, + "r_frame_rate": "30/1", + "nb_frames": "550", + "side_data_list": [ + {} + ] + } + ], + "format": { + "duration": "18.333333", + "size": "654403" + } + } + }, + "Pulsar-nvidia-native.mp4": { + "sha256": "ecec251eed0ab5bd9542871ab5ed79d4c28ef455d3a4e59a8510f618036369ec", + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "index": 0, + "codec_name": "h264", + "width": 1920, + "height": 1080, + "r_frame_rate": "60/1", + "nb_frames": "7414" + }, + { + "index": 1, + "codec_name": "aac", + "sample_rate": "48000", + "channels": 2, + "r_frame_rate": "0/0", + "nb_frames": "5792" + } + ], + "format": { + "duration": "123.566667", + "size": "94045686" + } + } + } + }, + "prismUiCapture": { + "frames": 112, + "lastFrameSeconds": 18.254805999999867, + "encoding": "variable capture timestamps, encoded at 30 fps with repeated frames; does not measure native preview fps" + }, + "sdkVersions": { + "afx": "1.6.1.2", + "vfx": "0.7.6.0" + } +} diff --git a/scripts/probe-nvidia-effects.py b/scripts/probe-nvidia-effects.py index 404da6f8..e40c7fba 100644 --- a/scripts/probe-nvidia-effects.py +++ b/scripts/probe-nvidia-effects.py @@ -83,7 +83,7 @@ async def caption(text): baseline_program = await image("NvidiaSDKRegression") report["video"] = [] for kind in KINDS[:3]: - await caption("NVIDIA / Pulsar - sortie native 1080p60\n"+kind+" - traitement actif (fixture synthetique)") + await caption("NVIDIA / Pulsar - sortie native 1080p60\n"+kind+" - activation puis mesure (fixture synthetique)") settings = {"mode": 1, "threshold": .8, "processing_interval": 1} if kind == KINDS[0] else {"intensity": .8} await wire.call("CreateSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/test", "filterKind": kind, "filterSettings": settings}) actual = await wire.call("GetSourceFilter", {"sourceName": "Stripes", "filterName": "sdk/test"}) From e51467937d65f935d1a598237287815bef04677f Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Sat, 10 Oct 2026 13:21:36 +0200 Subject: [PATCH 15/17] docs(nvidia): checkpoint native video and shared integration --- .../eleven/20261010T112000Z-checkpoint.json | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T112000Z-checkpoint.json diff --git a/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T112000Z-checkpoint.json b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T112000Z-checkpoint.json new file mode 100644 index 00000000..1bcd00bd --- /dev/null +++ b/evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T112000Z-checkpoint.json @@ -0,0 +1,36 @@ +{ + "repo": "D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261009-capability-audit", + "branch": "eleven/local-20261009-capability-audit", + "codeAndProofHead": "32d3830a0f18aa25243548a649bbbd84c02786f1", + "base": "c5011056f307424d5ce033972b7e07b0b07c45f5", + "changedFiles": [ + "README.md", + "docs/LIBOBS-CHANGES.md", + "evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-native-worker.json", + "evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-patch-replay.json", + "evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111100Z-sdk-intensity-experiment.json", + "evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-media-check.json", + "evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-media-probe.txt", + "evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-native-video.json", + "evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-report.md", + "evidence/local-20261010-nvidia-sdk-integration/eleven/20261010T111700Z-video-manifest.json", + "patches/0062-fix-nvidia-audio-settings-worker.patch", + "patches/README.md", + "plugins/pulsar-nv-secure-load/README.md", + "scripts/probe-nvidia-effects.py" + ], + "validation": [ + "MSVC targeted module build pass", + "NVIDIA patch replay pass", + "native installed SDK and lifecycle/video probe pass", + "H264/AAC decoded pixels and signal pass", + "Python compilation and packaging pass", + "workspace guard continue=true" + ], + "remaining": [ + "Full release build/CI", + "Physical quality and capacity qualification", + "Absent consumers listed in Prism aggregate" + ], + "retention": "Active continuation worktree retained while draft PR #301 is unmerged; existing cache preserved" +} From a7da349e3c00c7ceb9f85278fd9f6ecd5fe32d8f Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:05:43 +0200 Subject: [PATCH 16/17] feat(nvidia): AR camera effects, video denoise and native diagnostics Carry reviewed native patches for installed AR gaze/face tracking, VFX denoising and same-frame comparison. Load SDKs through validated locations, expose whole-device NVML samples and graphics-thread pipeline counters, and qualify demand processing and AFX lifecycle. Retain failed render-window evidence beside the unchanged passing rerun. SDK binaries and models stay external. --- README.md | 4 +- docs/LIBOBS-CHANGES.md | 38 +- ...40415Z-nvidia-ar-demand-qualification.json | 189 +++ ...415Z-nvidia-full-demand-qualification.json | 654 ++++++++++ ...010T140415Z-nvidia-full-demand-stable.json | 1154 +++++++++++++++++ .../20261010T140415Z-nvidia-patch-replay.json | 15 + ...15Z-nvidia-video-demand-qualification.json | 700 ++++++++++ .../20261010T140600Z-AGENT_CHECKPOINT.json | 19 + ...-nvidia-video-denoise-and-comparison.patch | 471 +++++++ ...dia-ar-effects-and-demand-processing.patch | 675 ++++++++++ patches/README.md | 8 +- plugins/pulsar-multi-stream/README.md | 2 + .../pulsar-multi-stream/src/plugin-main.cpp | 39 + plugins/pulsar-nv-secure-load/README.md | 19 +- .../pulsar-nv-secure-load.h | 23 + .../pulsar-nv-secure-load/pulsar-nvar-load.h | 84 ++ .../pulsar-nv-secure-load/pulsar-nvidia-gpu.h | 69 + scripts/check-nv-filters-packaging.py | 1 + scripts/probe-nvidia-ar-parity.py | 180 +++ scripts/probe-nvidia-video-parity.py | 133 ++ tests/nv-probe/nv-probe-test.c | 10 +- 21 files changed, 4481 insertions(+), 6 deletions(-) create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-ar-demand-qualification.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-full-demand-qualification.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-full-demand-stable.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-patch-replay.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-video-demand-qualification.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T140600Z-AGENT_CHECKPOINT.json create mode 100644 patches/0063-add-nvidia-video-denoise-and-comparison.patch create mode 100644 patches/0064-add-nvidia-ar-effects-and-demand-processing.patch create mode 100644 plugins/pulsar-nv-secure-load/pulsar-nvar-load.h create mode 100644 plugins/pulsar-nv-secure-load/pulsar-nvidia-gpu.h create mode 100644 scripts/probe-nvidia-ar-parity.py create mode 100644 scripts/probe-nvidia-video-parity.py diff --git a/README.md b/README.md index c821da97..b35e7889 100644 --- a/README.md +++ b/README.md @@ -473,7 +473,7 @@ does not recover scene identity from arbitrary pixels. Pulsar is a maintained native fork, not only a JavaScript wrapper around stock OBS. Its OBS submodule is pinned to `bd73b922891e56839b0bc86bdc519802802f9d68`, an OBS 32.1.2-derived revision. -Five foundational modifications are incorporated in that pin; **59 additional +Five foundational modifications are incorporated in that pin; **61 additional patch files** build the current media core. | Area | What Pulsar changes | @@ -598,7 +598,7 @@ as a troubleshooting shortcut. | [Client](packages/pulsar-client/README.md) | TypeScript API, typed events, errors and examples. | | [Embedding](docs/PRISM-EMBEDDING.md) | Host lifecycle, readiness and verified bundle integration. | | [Architecture](docs/ARCHITECTURE.md) | Runtime structure and ownership boundaries. | -| [All libobs changes](docs/LIBOBS-CHANGES.md) | Integrated OBS changes and all 59 source patches. | +| [All libobs changes](docs/LIBOBS-CHANGES.md) | Integrated OBS changes and all 61 source patches. | | [All documentation](docs/README.md) | Current guides, operational runbooks, contracts and historical studies. | | [Development](docs/DEVELOPMENT.md) | Build toolchain and local development. | | [Dual-lane canary](docs/runbooks/pulsar-dual-lane-canary.md) | Qualification and operational checks. | diff --git a/docs/LIBOBS-CHANGES.md b/docs/LIBOBS-CHANGES.md index aab22d82..c943f14c 100644 --- a/docs/LIBOBS-CHANGES.md +++ b/docs/LIBOBS-CHANGES.md @@ -28,7 +28,7 @@ missing files to recreate in `patches/`: | [7e7a5a383](https://github.com/ZabLaboratory/obs-studio/commit/7e7a5a383) | Adds a separate Program-return camera. | | [b33f831b8](https://github.com/ZabLaboratory/obs-studio/commit/b33f831b8) | Resolves embedded modules and registers Program return. | -On top of this pin, 3.0.0 carries **59 patch files**: 58 root-OBS patches +On top of this pin, 3.0.0 carries **61 patch files**: 60 root-OBS patches and one nested obs-browser patch. [build-win.ps1](../scripts/build-win.ps1) sorts complete filenames lexically, routes names containing `obs-browser` to `upstream/plugins/obs-browser/`, and applies the others to `upstream/`. @@ -625,6 +625,42 @@ Affected file: `plugins/nv-filters/nvidia-audiofx-filter.c`. ## Validation boundaries and retained studies +### 0063-add-nvidia-video-denoise-and-comparison + +[Source patch](../patches/0063-add-nvidia-video-denoise-and-comparison.patch) + +Registers `nv_denoise_filter` only with the installed conservative/aggressive +VFX models. Inputs use normalized planar BGR float and outputs convert back +to the existing GPU texture. Closed model modes 0/1, live blend intensity, +temporal state and exact zero bypass are native. Comparison preserves the +original left half and processes the right half of the same source frame; +it also appears in Program. An exported module flag gates the Prism setting. + +Affected files: `plugins/nv-filters/nvidia-videofx-filter.c`, `nv-filters.c`, +`data/rtx_blur.effect` and `data/rtx_greenscreen.effect` in the same directory. + +### 0064-add-nvidia-ar-effects-and-demand-processing + +[Source patch](../patches/0064-add-nvidia-ar-effects-and-demand-processing.patch) + +Adds `nv_autoframe_filter` and `nv_eye_contact_filter` using the separately +installed AR 0.8.7 SDK and existing VFX texture/CUDA interop. The owned minimal +ABI adapter lives outside upstream. Auto Frame follows the largest valid face, +clamps the crop and returns smoothly to the original frame after one second +without a face. Eye Contact binds the required gaze and landmark outputs; +strength blends the processed image and zero bypasses it exactly. + +Image/model allocation begins on actual render demand. Unused sources perform +no inference; warmed allocations remain retained for resumption. The native +`get_nvidia_processing` procedure reads processed counts and pipeline wall time +on the graphics thread. This measures the native pipeline, not GPU-only time. +Creation failures return to one cleanup owner; destroyed handles are nulled; +partial image allocations and both background-blur textures are released. + +Affected files: `plugins/nv-filters/nvidia-videofx-filter.c`, `nv-filters.c` +and `data/rtx_blur.effect`. The native probes use synthetic patterns and an +explicit official portrait fixture; real webcam quality is a separate check. + The native CPU readback study records 100 warm-up and 100 measured Cuts per trial, a changed decoded-image oracle, media timestamps and common Program audio. Measured x264 p95 moves from 45.897–45.947 ms in the reference trials diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-ar-demand-qualification.json b/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-ar-demand-qualification.json new file mode 100644 index 00000000..3ddcae9d --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-ar-demand-qualification.json @@ -0,0 +1,189 @@ +{ + "started": 1791640253.1636143, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "nvFiltersSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "fixture": { + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-ar-fixtures\\ar_001.png", + "sha256": "a196d219b924dcbb1b6c3849b781f15d61328a9066e3f49c71f689cb58594b8b" + }, + "nativeSdkCapability": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "ar": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.8.7.0", + "models_present": true, + "usable": true, + "version": "0.8.7.0" + }, + "module_loaded": true, + "same_frame_comparison": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "gpuBefore": { + "available": true, + "devices": [ + { + "gpuPercent": 14, + "index": 0, + "memoryPercent": 2, + "name": "NVIDIA GeForce RTX 3070", + "totalMemoryBytes": 8589934592, + "usedMemoryBytes": 4336930816, + "uuid": "GPU-a3fc3a1a-36da-1c8e-e8fb-e73e4ab35bbe" + } + ], + "reason": "available", + "scope": "whole-device" + }, + "demandProcessing": { + "cold": { + "available": true, + "imagesAllocated": false, + "lastProcessingMs": 0.0, + "processedFrames": 0, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "active": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 4.8667, + "processedFrames": 177, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "idleStart": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 4.8667, + "processedFrames": 177, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "idleEnd": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 4.8667, + "processedFrames": 177, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "resumed": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 5.0642, + "processedFrames": 298, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + } + }, + "tracking": [ + { + "position": "left", + "difference": 20.560814043209877 + }, + { + "position": "right", + "difference": 19.514062982253087 + } + ], + "timeline": [ + { + "seconds": 2.422000000005937, + "text": "NVIDIA Auto Frame / sujet a left\nSuivi natif du visage / zoom 2.5 maximum" + }, + { + "seconds": 5.953000000008615, + "text": "NVIDIA Auto Frame / sujet a right\nSuivi natif du visage / zoom 2.5 maximum" + }, + { + "seconds": 9.468000000008033, + "text": "NVIDIA Auto Frame / aucun visage\nRetour progressif au cadrage d'origine" + }, + { + "seconds": 14.422000000005937, + "text": "NVIDIA Eye Contact / comparaison meme image\nGAUCHE : avant / DROITE : correction du regard" + }, + { + "seconds": 22.875, + "text": "NVIDIA Eye Contact / intensite zero\nBypass exact de la correction" + } + ], + "noFaceFallback": { + "initialCropDifference": 16.10308159722222, + "finalDifference": 0.0125 + }, + "eyeContact": { + "leftDifference": 0.0, + "rightDifference": 0.2690799842395587, + "render": { + "frames": 301, + "skipped": 0, + "meanMs": 6.363509 + } + }, + "gpuWithEyeContact": { + "available": true, + "devices": [ + { + "gpuPercent": 39, + "index": 0, + "memoryPercent": 9, + "name": "NVIDIA GeForce RTX 3070", + "totalMemoryBytes": 8589934592, + "usedMemoryBytes": 4949839872, + "uuid": "GPU-a3fc3a1a-36da-1c8e-e8fb-e73e4ab35bbe" + } + ], + "reason": "available", + "scope": "whole-device" + }, + "nativeRecording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-ar-demand-qualification\\pulsar-20261010-155105.mp4", + "checks": [ + "cold allocation and processing only on render demand", + "idle processing stops and resumes", + "actual AR registration", + "face-follow and native zoom at two positions", + "eye redirection same-frame comparison", + "native render windows", + "exact bypass", + "cleanup and graceful shutdown" + ], + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T13:50:53Z INFO GDmJLc6buKF1dpSZ libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T13:50:53Z INFO GDmJLc6buKF1dpSZ libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T13:50:54Z INFO GDmJLc6buKF1dpSZ NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T13:50:54Z INFO GDmJLc6buKF1dpSZ NVIDIA Audio Effects: | enabled", + "2026-10-10T13:50:54Z INFO GDmJLc6buKF1dpSZ NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T13:50:54Z INFO GDmJLc6buKF1dpSZ NVIDIA VIDEO FX | : enabled, redistributable found" + ], + "sdkDiagnostics": [], + "qualification": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity", + "passed": true +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-full-demand-qualification.json b/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-full-demand-qualification.json new file mode 100644 index 00000000..0aec2981 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-full-demand-qualification.json @@ -0,0 +1,654 @@ +{ + "started": 1791640378.4704838, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "nvFiltersSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "mutedDeviceInputs": [ + "PulsarDesktopAudio" + ], + "capabilities": { + "active_encoder": "nvenc", + "audio_bitrate": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ], + "capabilities": { + "active_encoder": { + "applicability": "boot-fixed", + "value": "nvenc" + }, + "audio_bitrate": { + "applicability": "live", + "max": 512, + "min": 64, + "step": 32, + "values": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ] + }, + "audio_monitoring": { + "applicability": "live", + "available": true, + "device_bound": true, + "device_id": "{0.0.0.00000000}.{3a9b3ef5-04ae-4b95-88d7-53e24294bc68}", + "device_name": "Default", + "device_selectable": true + }, + "audio_sample_rate": { + "applicability": "read-only", + "hz": 48000 + }, + "audio_speaker_layout": { + "applicability": "read-only", + "channels": 2, + "layout": "stereo" + }, + "audio_tracks": { + "applicability": "read-only", + "bound": 1, + "count": 6, + "tracks": [ + { + "value": 1 + } + ] + }, + "destination_kinds": { + "applicability": "live", + "values": [ + { + "value": "rtmp_custom" + }, + { + "value": "vod_local" + }, + { + "value": "twitch" + }, + { + "value": "youtube" + } + ] + }, + "encoder_families": { + "applicability": "boot-fixed", + "values": [ + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 20, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "ultrafast" + }, + { + "value": "superfast" + }, + { + "value": "veryfast" + }, + { + "value": "faster" + }, + { + "value": "fast" + }, + { + "value": "medium" + }, + { + "value": "slow" + }, + { + "value": "slower" + }, + { + "value": "veryslow" + }, + { + "value": "placebo" + } + ], + "profiles": [ + { + "value": "baseline" + }, + { + "value": "main" + }, + { + "value": "high" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "VBR" + } + ], + "value": "x264" + }, + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 10, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "p1" + }, + { + "value": "p2" + }, + { + "value": "p3" + }, + { + "value": "p4" + }, + { + "value": "p5" + }, + { + "value": "p6" + }, + { + "value": "p7" + } + ], + "profiles": [ + { + "value": "high" + }, + { + "value": "main" + }, + { + "value": "baseline" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "CQP" + }, + { + "value": "VBR" + } + ], + "value": "nvenc" + } + ] + }, + "encoders": { + "applicability": "boot-fixed", + "values": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ] + }, + "filters": { + "applicability": "live", + "values": [ + { + "value": "nvidia_audiofx_filter" + }, + { + "value": "nv_greenscreen_filter" + }, + { + "value": "nv_blur_filter" + }, + { + "value": "nv_background_blur_filter" + }, + { + "value": "nv_denoise_filter" + }, + { + "value": "nv_autoframe_filter" + }, + { + "value": "nv_eye_contact_filter" + }, + { + "value": "mask_filter" + }, + { + "value": "mask_filter_v2" + }, + { + "value": "crop_filter" + }, + { + "value": "gain_filter" + }, + { + "value": "basic_eq_filter" + }, + { + "value": "hdr_tonemap_filter" + }, + { + "value": "color_filter" + }, + { + "value": "color_filter_v2" + }, + { + "value": "scale_filter" + }, + { + "value": "scroll_filter" + }, + { + "value": "gpu_delay" + }, + { + "value": "color_key_filter" + }, + { + "value": "color_key_filter_v2" + }, + { + "value": "clut_filter" + }, + { + "value": "sharpness_filter" + }, + { + "value": "sharpness_filter_v2" + }, + { + "value": "chroma_key_filter" + }, + { + "value": "chroma_key_filter_v2" + }, + { + "value": "async_delay_filter" + }, + { + "value": "noise_suppress_filter" + }, + { + "value": "noise_suppress_filter_v2" + }, + { + "value": "invert_polarity_filter" + }, + { + "value": "noise_gate_filter" + }, + { + "value": "compressor_filter" + }, + { + "value": "limiter_filter" + }, + { + "value": "expander_filter" + }, + { + "value": "upward_compressor_filter" + }, + { + "value": "luma_key_filter" + }, + { + "value": "luma_key_filter_v2" + }, + { + "value": "pulsar_vignette_filter" + } + ] + }, + "graphics_adapters": { + "active_index": 0, + "applicability": "read-only", + "values": [ + { + "index": 0, + "value": "NVIDIA GeForce RTX 3070" + } + ] + }, + "nv_filters": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "ar": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.8.7.0", + "models_present": true, + "usable": true, + "version": "0.8.7.0" + }, + "module_loaded": true, + "same_frame_comparison": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "output_scales": { + "applicability": "boot-fixed", + "canvas": { + "height": 1080, + "width": 1920 + }, + "values": [ + { + "height": 1080, + "scale": 1.0, + "value": "1920x1080", + "width": 1920 + } + ] + }, + "record_container": { + "applicability": "boot-fixed", + "value": "mp4", + "values": [ + { + "value": "mp4" + }, + { + "value": "mkv" + } + ] + }, + "record_markers": { + "add_chapter": false, + "applicability": "read-only", + "split_file": true + }, + "source_kinds": { + "applicability": "live", + "values": [ + { + "value": "audio_line" + }, + { + "value": "pulsar_preview_frame" + }, + { + "value": "image_source" + }, + { + "value": "color_source" + }, + { + "value": "color_source_v2" + }, + { + "value": "color_source_v3" + }, + { + "value": "slideshow" + }, + { + "value": "slideshow_v2" + }, + { + "value": "ffmpeg_source" + }, + { + "value": "text_gdiplus" + }, + { + "value": "text_gdiplus_v2" + }, + { + "value": "text_gdiplus_v3" + }, + { + "value": "browser_source" + }, + { + "value": "text_ft2_source" + }, + { + "value": "text_ft2_source_v2" + }, + { + "value": "vlc_source" + }, + { + "value": "monitor_capture" + }, + { + "value": "window_capture" + }, + { + "value": "game_capture" + }, + { + "value": "dshow_input" + }, + { + "value": "wasapi_input_capture" + }, + { + "value": "wasapi_output_capture" + }, + { + "value": "wasapi_process_output_capture" + } + ] + }, + "video_bitrate": { + "applicability": "live", + "max": 50000, + "min": 200, + "step": 50 + }, + "video_colorimetry": { + "applicability": "read-only", + "format": "NV12", + "range": "Partial", + "value": "709" + } + }, + "encoders": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ], + "version": 1, + "video_bitrate": { + "max": 50000, + "min": 200 + } + }, + "video": [ + { + "kind": "nv_greenscreen_filter", + "settings": { + "mode": 1, + "processing_interval": 1, + "threshold": 0.8 + }, + "pixelDifferenceMean": 137.625, + "alphaRange": [ + 0, + 0 + ], + "frameDelta": 304, + "skippedDelta": 0, + "meanRenderMs": 6.6333006, + "programScenePixelDifferenceMean": 137.625 + }, + { + "kind": "nv_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 301, + "skippedDelta": 1, + "meanRenderMs": 2.8061048, + "programScenePixelDifferenceMean": 58.8734375 + } + ], + "videoTimeline": [ + { + "seconds": 0.01600000000325963, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_greenscreen_filter - activation puis mesure (fixture synthetique)" + }, + { + "seconds": 8.812999999994645, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_blur_filter - activation puis mesure (fixture synthetique)" + } + ], + "failure": { + "type": "AssertionError", + "message": "{'kind': 'nv_blur_filter', 'settings': {'intensity': 0.8}, 'pixelDifferenceMean': 58.8734375, 'alphaRange': [255, 255], 'frameDelta': 301, 'skippedDelta': 1, 'meanRenderMs': 2.8061048, 'programScenePixelDifferenceMean': 58.8734375}" + }, + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T13:52:58Z INFO 9L7aM3Y5rD_W-sCs libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T13:52:58Z INFO 9L7aM3Y5rD_W-sCs libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T13:52:59Z INFO 9L7aM3Y5rD_W-sCs NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T13:52:59Z INFO 9L7aM3Y5rD_W-sCs NVIDIA Audio Effects: | enabled", + "2026-10-10T13:52:59Z INFO 9L7aM3Y5rD_W-sCs NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T13:52:59Z INFO 9L7aM3Y5rD_W-sCs NVIDIA VIDEO FX | : enabled, redistributable found" + ], + "sdkDiagnostics": [], + "qualification": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity", + "passed": false +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-full-demand-stable.json b/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-full-demand-stable.json new file mode 100644 index 00000000..22c296f9 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-full-demand-stable.json @@ -0,0 +1,1154 @@ +{ + "started": 1791640731.9704676, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "nvFiltersSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "mutedDeviceInputs": [ + "PulsarDesktopAudio" + ], + "capabilities": { + "active_encoder": "nvenc", + "audio_bitrate": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ], + "capabilities": { + "active_encoder": { + "applicability": "boot-fixed", + "value": "nvenc" + }, + "audio_bitrate": { + "applicability": "live", + "max": 512, + "min": 64, + "step": 32, + "values": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ] + }, + "audio_monitoring": { + "applicability": "live", + "available": true, + "device_bound": true, + "device_id": "{0.0.0.00000000}.{3a9b3ef5-04ae-4b95-88d7-53e24294bc68}", + "device_name": "Default", + "device_selectable": true + }, + "audio_sample_rate": { + "applicability": "read-only", + "hz": 48000 + }, + "audio_speaker_layout": { + "applicability": "read-only", + "channels": 2, + "layout": "stereo" + }, + "audio_tracks": { + "applicability": "read-only", + "bound": 1, + "count": 6, + "tracks": [ + { + "value": 1 + } + ] + }, + "destination_kinds": { + "applicability": "live", + "values": [ + { + "value": "rtmp_custom" + }, + { + "value": "vod_local" + }, + { + "value": "twitch" + }, + { + "value": "youtube" + } + ] + }, + "encoder_families": { + "applicability": "boot-fixed", + "values": [ + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 20, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "ultrafast" + }, + { + "value": "superfast" + }, + { + "value": "veryfast" + }, + { + "value": "faster" + }, + { + "value": "fast" + }, + { + "value": "medium" + }, + { + "value": "slow" + }, + { + "value": "slower" + }, + { + "value": "veryslow" + }, + { + "value": "placebo" + } + ], + "profiles": [ + { + "value": "baseline" + }, + { + "value": "main" + }, + { + "value": "high" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "VBR" + } + ], + "value": "x264" + }, + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 10, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "p1" + }, + { + "value": "p2" + }, + { + "value": "p3" + }, + { + "value": "p4" + }, + { + "value": "p5" + }, + { + "value": "p6" + }, + { + "value": "p7" + } + ], + "profiles": [ + { + "value": "high" + }, + { + "value": "main" + }, + { + "value": "baseline" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "CQP" + }, + { + "value": "VBR" + } + ], + "value": "nvenc" + } + ] + }, + "encoders": { + "applicability": "boot-fixed", + "values": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ] + }, + "filters": { + "applicability": "live", + "values": [ + { + "value": "nvidia_audiofx_filter" + }, + { + "value": "nv_greenscreen_filter" + }, + { + "value": "nv_blur_filter" + }, + { + "value": "nv_background_blur_filter" + }, + { + "value": "nv_denoise_filter" + }, + { + "value": "nv_autoframe_filter" + }, + { + "value": "nv_eye_contact_filter" + }, + { + "value": "mask_filter" + }, + { + "value": "mask_filter_v2" + }, + { + "value": "crop_filter" + }, + { + "value": "gain_filter" + }, + { + "value": "basic_eq_filter" + }, + { + "value": "hdr_tonemap_filter" + }, + { + "value": "color_filter" + }, + { + "value": "color_filter_v2" + }, + { + "value": "scale_filter" + }, + { + "value": "scroll_filter" + }, + { + "value": "gpu_delay" + }, + { + "value": "color_key_filter" + }, + { + "value": "color_key_filter_v2" + }, + { + "value": "clut_filter" + }, + { + "value": "sharpness_filter" + }, + { + "value": "sharpness_filter_v2" + }, + { + "value": "chroma_key_filter" + }, + { + "value": "chroma_key_filter_v2" + }, + { + "value": "async_delay_filter" + }, + { + "value": "noise_suppress_filter" + }, + { + "value": "noise_suppress_filter_v2" + }, + { + "value": "invert_polarity_filter" + }, + { + "value": "noise_gate_filter" + }, + { + "value": "compressor_filter" + }, + { + "value": "limiter_filter" + }, + { + "value": "expander_filter" + }, + { + "value": "upward_compressor_filter" + }, + { + "value": "luma_key_filter" + }, + { + "value": "luma_key_filter_v2" + }, + { + "value": "pulsar_vignette_filter" + } + ] + }, + "graphics_adapters": { + "active_index": 0, + "applicability": "read-only", + "values": [ + { + "index": 0, + "value": "NVIDIA GeForce RTX 3070" + } + ] + }, + "nv_filters": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "ar": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.8.7.0", + "models_present": true, + "usable": true, + "version": "0.8.7.0" + }, + "module_loaded": true, + "same_frame_comparison": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "output_scales": { + "applicability": "boot-fixed", + "canvas": { + "height": 1080, + "width": 1920 + }, + "values": [ + { + "height": 1080, + "scale": 1.0, + "value": "1920x1080", + "width": 1920 + } + ] + }, + "record_container": { + "applicability": "boot-fixed", + "value": "mp4", + "values": [ + { + "value": "mp4" + }, + { + "value": "mkv" + } + ] + }, + "record_markers": { + "add_chapter": false, + "applicability": "read-only", + "split_file": true + }, + "source_kinds": { + "applicability": "live", + "values": [ + { + "value": "audio_line" + }, + { + "value": "pulsar_preview_frame" + }, + { + "value": "image_source" + }, + { + "value": "color_source" + }, + { + "value": "color_source_v2" + }, + { + "value": "color_source_v3" + }, + { + "value": "slideshow" + }, + { + "value": "slideshow_v2" + }, + { + "value": "ffmpeg_source" + }, + { + "value": "text_gdiplus" + }, + { + "value": "text_gdiplus_v2" + }, + { + "value": "text_gdiplus_v3" + }, + { + "value": "browser_source" + }, + { + "value": "text_ft2_source" + }, + { + "value": "text_ft2_source_v2" + }, + { + "value": "vlc_source" + }, + { + "value": "monitor_capture" + }, + { + "value": "window_capture" + }, + { + "value": "game_capture" + }, + { + "value": "dshow_input" + }, + { + "value": "wasapi_input_capture" + }, + { + "value": "wasapi_output_capture" + }, + { + "value": "wasapi_process_output_capture" + } + ] + }, + "video_bitrate": { + "applicability": "live", + "max": 50000, + "min": 200, + "step": 50 + }, + "video_colorimetry": { + "applicability": "read-only", + "format": "NV12", + "range": "Partial", + "value": "709" + } + }, + "encoders": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ], + "version": 1, + "video_bitrate": { + "max": 50000, + "min": 200 + } + }, + "video": [ + { + "kind": "nv_greenscreen_filter", + "settings": { + "mode": 1, + "processing_interval": 1, + "threshold": 0.8 + }, + "pixelDifferenceMean": 137.625, + "alphaRange": [ + 0, + 0 + ], + "frameDelta": 304, + "skippedDelta": 0, + "meanRenderMs": 6.126601, + "programScenePixelDifferenceMean": 137.625 + }, + { + "kind": "nv_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 304, + "skippedDelta": 0, + "meanRenderMs": 1.8501366000000001, + "programScenePixelDifferenceMean": 58.8734375, + "liveIntensity": [ + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.2, + "pixelDifferenceMean": 58.86875 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + }, + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + } + ] + }, + { + "kind": "nv_background_blur_filter", + "settings": { + "intensity": 0.8 + }, + "pixelDifferenceMean": 58.8734375, + "alphaRange": [ + 255, + 255 + ], + "frameDelta": 304, + "skippedDelta": 0, + "meanRenderMs": 6.6049018, + "programScenePixelDifferenceMean": 58.8734375, + "liveIntensity": [ + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.2, + "pixelDifferenceMean": 58.86875 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + }, + { + "intensity": 0.0, + "pixelDifferenceMean": 0.0 + }, + { + "intensity": 0.8, + "pixelDifferenceMean": 58.8734375 + } + ] + } + ], + "videoTimeline": [ + { + "seconds": 0.0, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_greenscreen_filter - activation puis mesure (fixture synthetique)" + }, + { + "seconds": 8.843000000008033, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_blur_filter - activation puis mesure (fixture synthetique)" + }, + { + "seconds": 20.514999999999418, + "text": "NVIDIA / Pulsar - sortie native 1080p60\nnv_background_blur_filter - activation puis mesure (fixture synthetique)" + }, + { + "seconds": 34.34300000000803, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 1/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 34.562000000005355, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 2/20 : NvidiaSDKRegression" + }, + { + "seconds": 34.78100000000268, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 3/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 35.01499999999942, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 4/20 : NvidiaSDKRegression" + }, + { + "seconds": 35.23400000001129, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 5/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 35.437000000005355, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 6/20 : NvidiaSDKRegression" + }, + { + "seconds": 35.63999999999942, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 7/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 35.828000000008615, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 8/20 : NvidiaSDKRegression" + }, + { + "seconds": 36.04700000000594, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 9/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 36.28100000000268, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 10/20 : NvidiaSDKRegression" + }, + { + "seconds": 36.5, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 11/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 36.703000000008615, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 12/20 : NvidiaSDKRegression" + }, + { + "seconds": 36.90600000000268, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 13/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 37.10900000001129, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 14/20 : NvidiaSDKRegression" + }, + { + "seconds": 37.312000000005355, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 15/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 37.54700000000594, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 16/20 : NvidiaSDKRegression" + }, + { + "seconds": 37.75, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 17/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 37.96800000000803, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 18/20 : NvidiaSDKRegression" + }, + { + "seconds": 38.17200000000594, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 19/20 : NvidiaSDKRegressionB" + }, + { + "seconds": 38.35900000001129, + "text": "NVIDIA / Pulsar - 2 effets video simultanes\nSwitch natif 20/20 : NvidiaSDKRegression" + }, + { + "seconds": 41.75, + "text": "NVIDIA / Pulsar - audio synthetique 48 kHz\nSignal original : magnitude native 0.07977" + }, + { + "seconds": 41.75, + "text": "NVIDIA AFX / denoiser\nIntensite 100 % / VAD OFF - bruit synthetique, aucune voix" + }, + { + "seconds": 46.76499999999942, + "text": "NVIDIA AFX / denoiser - demande 75%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 49.92200000000594, + "text": "NVIDIA AFX / denoiser - intensite 75%\nMagnitude native : 0.02326 / original : 0.07977" + }, + { + "seconds": 49.92200000000594, + "text": "NVIDIA AFX / denoiser - demande 25%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 52.92200000000594, + "text": "NVIDIA AFX / denoiser - intensite 25%\nMagnitude native : 0.05987 / original : 0.07977" + }, + { + "seconds": 52.92200000000594, + "text": "NVIDIA AFX / denoiser - demande 0 %\nBypass PCM / mesure native en cours" + }, + { + "seconds": 55.92200000000594, + "text": "NVIDIA AFX / denoiser - intensite 0 %\nBypass PCM : 0.07942 / original : 0.07977" + }, + { + "seconds": 55.92200000000594, + "text": "NVIDIA AFX / denoiser\nFiltre desactive : controle du signal original" + }, + { + "seconds": 58.562000000005355, + "text": "NVIDIA AFX / denoiser - reappliquer 100 %\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 61.578000000008615, + "text": "NVIDIA AFX / dereverb\nIntensite 100 % / VAD OFF - bruit synthetique, aucune voix" + }, + { + "seconds": 66.60900000001129, + "text": "NVIDIA AFX / dereverb - demande 75%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 69.75, + "text": "NVIDIA AFX / dereverb - intensite 75%\nMagnitude native : 0.03363 / original : 0.07977" + }, + { + "seconds": 69.75, + "text": "NVIDIA AFX / dereverb - demande 25%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 72.75, + "text": "NVIDIA AFX / dereverb - intensite 25%\nMagnitude native : 0.06841 / original : 0.07977" + }, + { + "seconds": 72.75, + "text": "NVIDIA AFX / dereverb - demande 0 %\nBypass PCM / mesure native en cours" + }, + { + "seconds": 75.88999999999942, + "text": "NVIDIA AFX / dereverb - intensite 0 %\nBypass PCM : 0.07947 / original : 0.07977" + }, + { + "seconds": 75.88999999999942, + "text": "NVIDIA AFX / dereverb\nFiltre desactive : controle du signal original" + }, + { + "seconds": 78.53100000000268, + "text": "NVIDIA AFX / dereverb - reappliquer 100 %\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 81.53100000000268, + "text": "NVIDIA AFX / dereverb_denoiser\nIntensite 100 % / VAD OFF - bruit synthetique, aucune voix" + }, + { + "seconds": 86.68700000000536, + "text": "NVIDIA AFX / dereverb_denoiser - demande 75%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 89.84300000000803, + "text": "NVIDIA AFX / dereverb_denoiser - intensite 75%\nMagnitude native : 0.02607 / original : 0.07977" + }, + { + "seconds": 89.84300000000803, + "text": "NVIDIA AFX / dereverb_denoiser - demande 25%\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 92.98400000001129, + "text": "NVIDIA AFX / dereverb_denoiser - intensite 25%\nMagnitude native : 0.05935 / original : 0.07977" + }, + { + "seconds": 92.98400000001129, + "text": "NVIDIA AFX / dereverb_denoiser - demande 0 %\nBypass PCM / mesure native en cours" + }, + { + "seconds": 95.98400000001129, + "text": "NVIDIA AFX / dereverb_denoiser - intensite 0 %\nBypass PCM : 0.07934 / original : 0.07977" + }, + { + "seconds": 95.98400000001129, + "text": "NVIDIA AFX / dereverb_denoiser\nFiltre desactive : controle du signal original" + }, + { + "seconds": 98.60900000001129, + "text": "NVIDIA AFX / dereverb_denoiser - reappliquer 100 %\nChargement asynchrone puis mesure native du signal" + }, + { + "seconds": 101.60900000001129, + "text": "NVIDIA AFX - test de cycle de vie\n72 reglages rapides et 6 retraits pendant chargement" + }, + { + "seconds": 102.01499999999942, + "text": "NVIDIA AFX - deux filtres audio\nRetrait du premier / le second doit rester operationnel" + }, + { + "seconds": 110.18700000000536, + "text": "NVIDIA AFX - VAD et continuite\nIntensite 25 % / VAD ON puis OFF / qualite voix non evaluee" + }, + { + "seconds": 118.20300000000861, + "text": "NVIDIA AFX - bypass a 0 % avec VAD ON\nLe signal original doit continuer" + }, + { + "seconds": 121.34300000000803, + "text": "NVIDIA / Pulsar - fin de qualification\n72 mises a jour rapides / retrait pendant chargement / deux filtres audio" + } + ], + "stackedSceneSwitches": [ + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegressionB", + "pixelDifferenceMean": 58.871875 + }, + { + "scene": "NvidiaSDKRegression", + "pixelDifferenceMean": 58.871875 + } + ], + "stackedStats": { + "frameDelta": 253, + "skippedDelta": 0, + "lastRenderMs": 8.6945 + }, + "audio": { + "fixture": "seeded white noise, raw native volume-meter magnitude", + "baseline": 0.07977056803722535, + "methods": [ + { + "method": "denoiser", + "magnitude": 0.004844810197373492, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.023262887567451315 + }, + { + "intensity": 0.25, + "magnitude": 0.05987199729210452 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.07942249247078169, + "disabledMagnitude": 0.07989226582531746, + "liveIntensityOneMagnitude": 0.009382878217691588, + "liveIntensityPassed": true + }, + { + "method": "dereverb", + "magnitude": 0.02832712320021677, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.03363340013172655 + }, + { + "intensity": 0.25, + "magnitude": 0.0684086854794416 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.0794697070463759, + "disabledMagnitude": 0.08000668292900301, + "liveIntensityOneMagnitude": 0.0451063639247569, + "liveIntensityPassed": true + }, + { + "method": "dereverb_denoiser", + "magnitude": 0.005831903140334873, + "intermediateIntensities": [ + { + "intensity": 0.75, + "magnitude": 0.026065905397563063 + }, + { + "intensity": 0.25, + "magnitude": 0.05935194060927437 + } + ], + "intermediateIntensityPassed": true, + "liveIntensityZeroMagnitude": 0.0793395636571666, + "disabledMagnitude": 0.0800711578474595, + "liveIntensityOneMagnitude": 0.010887042412400082, + "liveIntensityPassed": true + } + ], + "liveIntensityPassed": true + }, + "audioLifecycle": [ + { + "cycle": 0, + "updates": 12, + "maxUpdateMs": 0.48770000285003334, + "removeMs": 1.872599998023361 + }, + { + "cycle": 1, + "updates": 12, + "maxUpdateMs": 0.5825999978696927, + "removeMs": 1.9718999974429607 + }, + { + "cycle": 2, + "updates": 12, + "maxUpdateMs": 0.538399996003136, + "removeMs": 1.9494000007398427 + }, + { + "cycle": 3, + "updates": 12, + "maxUpdateMs": 0.38920000952202827, + "removeMs": 2.029499999480322 + }, + { + "cycle": 4, + "updates": 12, + "maxUpdateMs": 0.4481000069063157, + "removeMs": 1.9062999926973134 + }, + { + "cycle": 5, + "updates": 12, + "maxUpdateMs": 0.5713000136893243, + "removeMs": 1.8146999937016517 + } + ], + "audioSurvivingFilterMagnitude": 0.03910882312168435, + "audioLiveVad": { + "enabledMagnitude": 0.05965653557000281, + "disabledMagnitude": 0.06000442100287993, + "enabledSettings": { + "intensity": 0.25, + "method": "denoiser", + "vad": true + }, + "disabledSettings": { + "intensity": 0.25, + "method": "denoiser", + "vad": false + }, + "scope": "live reload/readback and signal continuity; white noise cannot qualify voice detection accuracy", + "zeroWithVadMagnitude": 0.0793124427718501 + }, + "nativeRecording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-full-demand-stable\\pulsar-20261010-155854.mp4", + "checks": [ + "SDK usable/version", + "four filters registered", + "three VFX source and Program scene pixel changes and exact bypass", + "live VFX intensity", + "20 scene switches with stacked VFX", + "native render timings", + "three AFX model methods", + "noise suppression signal", + "filter removal" + ], + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T13:58:52Z INFO kkHENoqeloyefjpY libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T13:58:52Z INFO kkHENoqeloyefjpY libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T13:58:52Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T13:58:52Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: | enabled", + "2026-10-10T13:58:52Z INFO kkHENoqeloyefjpY NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T13:58:52Z INFO kkHENoqeloyefjpY NVIDIA VIDEO FX | : enabled, redistributable found", + "2026-10-10T13:59:36Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T13:59:56Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T14:00:16Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/audio' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T14:00:36Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T14:00:36Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T14:00:36Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T14:00:36Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T14:00:36Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T14:00:36Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/stress' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T14:00:36Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/first' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "2026-10-10T14:00:36Z INFO kkHENoqeloyefjpY NVIDIA Audio Effects: 'sdk/second' | NVAFX SDK redist path was found here C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK" + ], + "sdkDiagnostics": [], + "qualification": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity", + "passed": true +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-patch-replay.json b/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-patch-replay.json new file mode 100644 index 00000000..cf929e64 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-patch-replay.json @@ -0,0 +1,15 @@ +{ + "baseline": "6fc5905315af589cde8dc1f7c85212d5ee6ad65a", + "nativeHead": "1246de1a4", + "patches": [ + "0063", + "0064" + ], + "normalizedSourceEquality": { + "plugins/nv-filters/nvidia-videofx-filter.c": true, + "plugins/nv-filters/nv-filters.c": true, + "plugins/nv-filters/data/rtx_blur.effect": true, + "plugins/nv-filters/data/rtx_greenscreen.effect": true + }, + "scope": "Incremental clean replay above previously applied 0062; excludes full package build and CI." +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-video-demand-qualification.json b/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-video-demand-qualification.json new file mode 100644 index 00000000..fc1670e2 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T140415Z-nvidia-video-demand-qualification.json @@ -0,0 +1,700 @@ +{ + "started": 1791640327.813564, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "nvFiltersSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "capabilities": { + "active_encoder": "nvenc", + "audio_bitrate": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ], + "capabilities": { + "active_encoder": { + "applicability": "boot-fixed", + "value": "nvenc" + }, + "audio_bitrate": { + "applicability": "live", + "max": 512, + "min": 64, + "step": 32, + "values": [ + { + "value": 64 + }, + { + "value": 96 + }, + { + "value": 128 + }, + { + "value": 160 + }, + { + "value": 192 + }, + { + "value": 224 + }, + { + "value": 256 + }, + { + "value": 288 + }, + { + "value": 320 + }, + { + "value": 352 + }, + { + "value": 384 + }, + { + "value": 416 + }, + { + "value": 448 + }, + { + "value": 480 + }, + { + "value": 512 + } + ] + }, + "audio_monitoring": { + "applicability": "live", + "available": true, + "device_bound": true, + "device_id": "{0.0.0.00000000}.{3a9b3ef5-04ae-4b95-88d7-53e24294bc68}", + "device_name": "Default", + "device_selectable": true + }, + "audio_sample_rate": { + "applicability": "read-only", + "hz": 48000 + }, + "audio_speaker_layout": { + "applicability": "read-only", + "channels": 2, + "layout": "stereo" + }, + "audio_tracks": { + "applicability": "read-only", + "bound": 1, + "count": 6, + "tracks": [ + { + "value": 1 + } + ] + }, + "destination_kinds": { + "applicability": "live", + "values": [ + { + "value": "rtmp_custom" + }, + { + "value": "vod_local" + }, + { + "value": "twitch" + }, + { + "value": "youtube" + } + ] + }, + "encoder_families": { + "applicability": "boot-fixed", + "values": [ + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 20, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "ultrafast" + }, + { + "value": "superfast" + }, + { + "value": "veryfast" + }, + { + "value": "faster" + }, + { + "value": "fast" + }, + { + "value": "medium" + }, + { + "value": "slow" + }, + { + "value": "slower" + }, + { + "value": "veryslow" + }, + { + "value": "placebo" + } + ], + "profiles": [ + { + "value": "baseline" + }, + { + "value": "main" + }, + { + "value": "high" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "VBR" + } + ], + "value": "x264" + }, + { + "bitrate": { + "max": 50000, + "min": 200, + "step": 50 + }, + "keyint_sec": { + "max": 10, + "min": 0, + "step": 1 + }, + "presets": [ + { + "value": "p1" + }, + { + "value": "p2" + }, + { + "value": "p3" + }, + { + "value": "p4" + }, + { + "value": "p5" + }, + { + "value": "p6" + }, + { + "value": "p7" + } + ], + "profiles": [ + { + "value": "high" + }, + { + "value": "main" + }, + { + "value": "baseline" + } + ], + "rate_controls": [ + { + "value": "CBR" + }, + { + "value": "CQP" + }, + { + "value": "VBR" + } + ], + "value": "nvenc" + } + ] + }, + "encoders": { + "applicability": "boot-fixed", + "values": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ] + }, + "filters": { + "applicability": "live", + "values": [ + { + "value": "nvidia_audiofx_filter" + }, + { + "value": "nv_greenscreen_filter" + }, + { + "value": "nv_blur_filter" + }, + { + "value": "nv_background_blur_filter" + }, + { + "value": "nv_denoise_filter" + }, + { + "value": "nv_autoframe_filter" + }, + { + "value": "nv_eye_contact_filter" + }, + { + "value": "mask_filter" + }, + { + "value": "mask_filter_v2" + }, + { + "value": "crop_filter" + }, + { + "value": "gain_filter" + }, + { + "value": "basic_eq_filter" + }, + { + "value": "hdr_tonemap_filter" + }, + { + "value": "color_filter" + }, + { + "value": "color_filter_v2" + }, + { + "value": "scale_filter" + }, + { + "value": "scroll_filter" + }, + { + "value": "gpu_delay" + }, + { + "value": "color_key_filter" + }, + { + "value": "color_key_filter_v2" + }, + { + "value": "clut_filter" + }, + { + "value": "sharpness_filter" + }, + { + "value": "sharpness_filter_v2" + }, + { + "value": "chroma_key_filter" + }, + { + "value": "chroma_key_filter_v2" + }, + { + "value": "async_delay_filter" + }, + { + "value": "noise_suppress_filter" + }, + { + "value": "noise_suppress_filter_v2" + }, + { + "value": "invert_polarity_filter" + }, + { + "value": "noise_gate_filter" + }, + { + "value": "compressor_filter" + }, + { + "value": "limiter_filter" + }, + { + "value": "expander_filter" + }, + { + "value": "upward_compressor_filter" + }, + { + "value": "luma_key_filter" + }, + { + "value": "luma_key_filter_v2" + }, + { + "value": "pulsar_vignette_filter" + } + ] + }, + "graphics_adapters": { + "active_index": 0, + "applicability": "read-only", + "values": [ + { + "index": 0, + "value": "NVIDIA GeForce RTX 3070" + } + ] + }, + "nv_filters": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "ar": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.8.7.0", + "models_present": true, + "usable": true, + "version": "0.8.7.0" + }, + "module_loaded": true, + "same_frame_comparison": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "output_scales": { + "applicability": "boot-fixed", + "canvas": { + "height": 1080, + "width": 1920 + }, + "values": [ + { + "height": 1080, + "scale": 1.0, + "value": "1920x1080", + "width": 1920 + } + ] + }, + "record_container": { + "applicability": "boot-fixed", + "value": "mp4", + "values": [ + { + "value": "mp4" + }, + { + "value": "mkv" + } + ] + }, + "record_markers": { + "add_chapter": false, + "applicability": "read-only", + "split_file": true + }, + "source_kinds": { + "applicability": "live", + "values": [ + { + "value": "audio_line" + }, + { + "value": "pulsar_preview_frame" + }, + { + "value": "image_source" + }, + { + "value": "color_source" + }, + { + "value": "color_source_v2" + }, + { + "value": "color_source_v3" + }, + { + "value": "slideshow" + }, + { + "value": "slideshow_v2" + }, + { + "value": "ffmpeg_source" + }, + { + "value": "text_gdiplus" + }, + { + "value": "text_gdiplus_v2" + }, + { + "value": "text_gdiplus_v3" + }, + { + "value": "browser_source" + }, + { + "value": "text_ft2_source" + }, + { + "value": "text_ft2_source_v2" + }, + { + "value": "vlc_source" + }, + { + "value": "monitor_capture" + }, + { + "value": "window_capture" + }, + { + "value": "game_capture" + }, + { + "value": "dshow_input" + }, + { + "value": "wasapi_input_capture" + }, + { + "value": "wasapi_output_capture" + }, + { + "value": "wasapi_process_output_capture" + } + ] + }, + "video_bitrate": { + "applicability": "live", + "max": 50000, + "min": 200, + "step": 50 + }, + "video_colorimetry": { + "applicability": "read-only", + "format": "NV12", + "range": "Partial", + "value": "709" + } + }, + "encoders": [ + { + "value": "nvenc" + }, + { + "value": "x264" + } + ], + "version": 1, + "video_bitrate": { + "max": 50000, + "min": 200 + } + }, + "denoise": [ + { + "mode": 0, + "meanDifference": 9.825851658950617, + "originalNoiseStd": 14.884593810233298, + "processedNoiseStd": 14.182592153361886, + "alphaPreserved": true, + "comparison": { + "leftDifference": 0.0, + "rightDifference": 9.82440110323089 + }, + "render": { + "frames": 180, + "skipped": 0, + "meanMs": 4.777442 + } + }, + { + "mode": 1, + "meanDifference": 9.13172164351852, + "originalNoiseStd": 14.884593810233298, + "processedNoiseStd": 6.921923822341412, + "alphaPreserved": true, + "comparison": { + "leftDifference": 0.0, + "rightDifference": 9.129181442080379 + }, + "render": { + "frames": 181, + "skipped": 0, + "meanMs": 4.96455 + } + } + ], + "timeline": [ + { + "seconds": 2.077999999994063, + "text": "NVIDIA / Video Noise Removal\nMode 0 - intensite 100 %" + }, + { + "seconds": 5.25, + "text": "NVIDIA / comparaison meme image\nGAUCHE : avant / DROITE : traitement mode 0" + }, + { + "seconds": 9.686999999990803, + "text": "NVIDIA / Video Noise Removal\nMode 1 - intensite 100 %" + }, + { + "seconds": 12.952999999994063, + "text": "NVIDIA / comparaison meme image\nGAUCHE : avant / DROITE : traitement mode 1" + }, + { + "seconds": 17.468999999997322, + "text": "NVIDIA / Video Noise Removal\nIntensite 0 % : bypass exact" + }, + { + "seconds": 27.406000000002678, + "text": "NVIDIA / fin du test\n2 modes / intensite / bypass / comparaison des 4 VFX" + } + ], + "liveIntensity": [ + { + "intensity": 0.25, + "difference": 2.3606052276234566 + }, + { + "intensity": 0.75, + "difference": 6.893694058641976 + } + ], + "comparisonKinds": [ + { + "kind": "nv_blur_filter", + "leftDifference": 0.0, + "rightDifference": 10.776561268715524 + }, + { + "kind": "nv_background_blur_filter", + "leftDifference": 0.0, + "rightDifference": 10.776561268715524 + }, + { + "kind": "nv_greenscreen_filter", + "leftDifference": 0.0, + "rightDifference": 140.07944050433412 + } + ], + "nativeRecording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-video-demand-qualification\\pulsar-20261010-155210.mp4", + "checks": [ + "actual SDK denoising and temporal state", + "two denoise models reduce synthetic noise", + "same-frame left/right comparison for four VFX", + "live blend intensity and exact bypass", + "native Program pixels", + "1080p60 render windows", + "cleanup and graceful shutdown" + ], + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T13:52:07Z INFO 8zoBCw8eTwNhhMWK libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T13:52:07Z INFO 8zoBCw8eTwNhhMWK libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T13:52:08Z INFO 8zoBCw8eTwNhhMWK NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T13:52:08Z INFO 8zoBCw8eTwNhhMWK NVIDIA Audio Effects: | enabled", + "2026-10-10T13:52:08Z INFO 8zoBCw8eTwNhhMWK NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T13:52:08Z INFO 8zoBCw8eTwNhhMWK NVIDIA VIDEO FX | : enabled, redistributable found" + ], + "sdkDiagnostics": [], + "qualification": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity", + "passed": true +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T140600Z-AGENT_CHECKPOINT.json b/evidence/local-20261009-capability-audit/eleven/20261010T140600Z-AGENT_CHECKPOINT.json new file mode 100644 index 00000000..477f9d31 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T140600Z-AGENT_CHECKPOINT.json @@ -0,0 +1,19 @@ +{ + "worktree": "D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261009-capability-audit", + "branch": "eleven/local-20261009-capability-audit", + "base": "e51467937d65f935d1a598237287815bef04677f", + "nativeSource": "1246de1a4f0df3f2", + "nvFiltersSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "publisherSha256": "e231c260fcb5d145b50aac9f8f24a022d3e4ada7bde2af92345e9e23bfa02991", + "validation": { + "nativeAR": "cold/idle/resume, face follow, gaze, bypass and 1080p60 passed", + "nativeAudioVFX": "three audio methods, intensity, removals during loading and 20 switches passed", + "nativeDenoise": "two models, blend and same-frame splits passed", + "loaderGate": "49 checks passed; real-SDK assumption repaired", + "focusedBuild": "nv-filters and pulsar-multi-stream passed", + "patchReplay": "0063/0064 match normalized native sources", + "packaging": "no SDK payload source gate passed" + }, + "remaining": "Shared dev integration and additional AR pair qualification", + "scope": "Focused native builds; no full-package CI qualification" +} diff --git a/patches/0063-add-nvidia-video-denoise-and-comparison.patch b/patches/0063-add-nvidia-video-denoise-and-comparison.patch new file mode 100644 index 00000000..42e402c6 --- /dev/null +++ b/patches/0063-add-nvidia-video-denoise-and-comparison.patch @@ -0,0 +1,471 @@ +From dedb9f8f2bcf005d22f31900c730cde36c2a5c68 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?ClodoCap=C3=A9o?= + <159788250+ClodoCapeo@users.noreply.github.com> +Date: Sat, 10 Oct 2026 15:12:26 +0200 +Subject: [PATCH] feat(nvidia): native video noise removal and same-frame + comparison + +Continue local-20261010 NVIDIA parity. Reuse the private upstream GPU interop and image/state lifetime for the Denoising SDK consumer; no second camera, CPU pixel readback or redistributed SDK. Expose two model strengths and independently blended intensity, exact zero bypass, original alpha and same-frame split. This private filter pipeline change is maintained as a downstream patch and is a candidate for upstream contribution after qualification. +--- + plugins/nv-filters/data/rtx_blur.effect | 5 +- + .../nv-filters/data/rtx_greenscreen.effect | 3 + + plugins/nv-filters/nv-filters.c | 9 + + plugins/nv-filters/nvidia-videofx-filter.c | 167 ++++++++++++++++-- + 4 files changed, 165 insertions(+), 19 deletions(-) + +diff --git a/plugins/nv-filters/data/rtx_blur.effect b/plugins/nv-filters/data/rtx_blur.effect +index 32ab2df80..89824b8c2 100644 +--- a/plugins/nv-filters/data/rtx_blur.effect ++++ b/plugins/nv-filters/data/rtx_blur.effect +@@ -3,6 +3,8 @@ + uniform float4x4 ViewProj; + uniform texture2d image; + uniform float multiplier; ++uniform float compare; ++uniform float processed_mix; + uniform texture2d mask; + uniform texture2d blurred; + +@@ -57,7 +59,8 @@ float4 Mask(FragData f_in) + float3 blur = srgb_nonlinear_to_linear(blurred.Sample(texSampler, f_in.uv).rgb); + // Between blurred and unblurred area we interpolate to have a smooth transition. + float m = smoothstep(0.85, 0.95, mask.Sample(texSampler, f_in.uv).a); +- float3 out_lin = lerp(blur, rgba.rgb, m); ++ float mix_amount = (compare > 0.5 && f_in.uv.x < 0.5) ? 0.0 : processed_mix; ++ float3 out_lin = lerp(rgba.rgb, lerp(blur, rgba.rgb, m), mix_amount); + return float4(out_lin, rgba.a); + } + +diff --git a/plugins/nv-filters/data/rtx_greenscreen.effect b/plugins/nv-filters/data/rtx_greenscreen.effect +index fc945d232..df93ed88f 100644 +--- a/plugins/nv-filters/data/rtx_greenscreen.effect ++++ b/plugins/nv-filters/data/rtx_greenscreen.effect +@@ -3,6 +3,7 @@ + uniform float4x4 ViewProj; + uniform texture2d image; + uniform float multiplier; ++uniform float compare; + + uniform texture2d mask; + uniform float threshold; +@@ -55,6 +56,8 @@ FragPos VSConvertUnorm(uint id : VERTEXID) + float4 Mask(FragData f_in) + { + float4 rgba = image.Sample(texSampler, f_in.uv); ++ if (compare > 0.5 && f_in.uv.x < 0.5) ++ return rgba; + rgba *= smoothstep(threshold - 0.1,threshold,mask.Sample(texSampler, f_in.uv).a); + return rgba; + } +diff --git a/plugins/nv-filters/nv-filters.c b/plugins/nv-filters/nv-filters.c +index 57203c4cb..411f77ec6 100644 +--- a/plugins/nv-filters/nv-filters.c ++++ b/plugins/nv-filters/nv-filters.c +@@ -5,6 +5,11 @@ + + OBS_DECLARE_MODULE() + OBS_MODULE_USE_DEFAULT_LOCALE("nv-filters", "en-US") ++/* Host capability probe: version 1 includes the native split comparator. */ ++MODULE_EXPORT uint32_t pulsar_nv_feature_flags(void) ++{ ++ return 1u; ++} + MODULE_EXPORT const char *obs_module_description(void) + { + return "NVIDIA filters"; +@@ -19,6 +24,8 @@ extern void unload_nvidia_afx(void); + extern struct obs_source_info nvidia_greenscreen_filter_info; + extern struct obs_source_info nvidia_blur_filter_info; + extern struct obs_source_info nvidia_background_blur_filter_info; ++extern struct obs_source_info nvidia_denoise_filter_info; ++extern bool nvvfx_denoise_models_available(void); + extern bool load_nvidia_vfx(void); + extern void unload_nvidia_vfx(void); + #endif +@@ -69,6 +76,8 @@ bool obs_module_load(void) + obs_register_source(&nvidia_greenscreen_filter_info); + obs_register_source(&nvidia_blur_filter_info); + obs_register_source(&nvidia_background_blur_filter_info); ++ if (nvvfx_denoise_models_available()) ++ obs_register_source(&nvidia_denoise_filter_info); + } + #endif + return true; +diff --git a/plugins/nv-filters/nvidia-videofx-filter.c b/plugins/nv-filters/nvidia-videofx-filter.c +index 9e80a59b9..60421f8a5 100644 +--- a/plugins/nv-filters/nvidia-videofx-filter.c ++++ b/plugins/nv-filters/nvidia-videofx-filter.c +@@ -45,7 +45,7 @@ + #define S_STRENGTH_DEFAULT 0.5 + #define TEXT_MODE_BLUR_STRENGTH MT_("Nvvfx.Method.Blur.Strength") + +-enum nvvfx_fx_id { S_FX_AIGS, S_FX_BLUR, S_FX_BG_BLUR }; ++enum nvvfx_fx_id { S_FX_AIGS, S_FX_BLUR, S_FX_BG_BLUR, S_FX_DENOISE }; + + bool nvvfx_loaded = false; + bool nvvfx_new_sdk = false; +@@ -84,6 +84,8 @@ struct nvvfx_data { + gs_eparam_t *threshold_param; + gs_eparam_t *multiplier_param; + float threshold; ++ bool compare; ++ bool denoise_reset; + + /* Every nth frame is processed through the FX (where n = + * processing_interval) so that the same mask is used for n consecutive +@@ -123,15 +125,58 @@ static const char *nv_background_blur_filter_name(void *unused) + return obs_module_text("Nvvfx.Method.BackgroundBlurFilter"); + } + ++static const char *nv_denoise_filter_name(void *unused) ++{ ++ UNUSED_PARAMETER(unused); ++ return "NVIDIA Video Noise Removal"; ++} ++ ++/* The SDK family may be installed without the optional denoising models. */ ++bool nvvfx_denoise_models_available(void) ++{ ++ char sdk[MAX_PATH], pattern[MAX_PATH]; ++ WIN32_FIND_DATAA item; ++ if (!nvvfx_get_sdk_path(sdk, MAX_PATH)) ++ return false; ++ const char *models[] = {"Denoise-con-*.trtpkg", "Denoise-agg-*.trtpkg"}; ++ for (size_t i = 0; i < 2; ++i) { ++ snprintf(pattern, sizeof(pattern), "%s\\models\\%s", sdk, models[i]); ++ HANDLE search = FindFirstFileA(pattern, &item); ++ if (search == INVALID_HANDLE_VALUE) ++ return false; ++ bool valid = !(item.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) && ++ (item.nFileSizeHigh || item.nFileSizeLow); ++ FindClose(search); ++ if (!valid) ++ return false; ++ } ++ return true; ++} ++ + static void nvvfx_filter_update(void *data, obs_data_t *settings) + { + struct nvvfx_data *filter = (struct nvvfx_data *)data; + NvCV_Status vfxErr; + enum nvvfx_fx_id id = filter->filter_id; + ++ filter->compare = obs_data_get_bool(settings, "compare"); + filter->threshold = (float)obs_data_get_double(settings, S_THRESHOLDFX); + filter->processing_interval = (int)obs_data_get_int(settings, S_PROCESSING); + float strength = (float)obs_data_get_double(settings, S_STRENGTH); ++ if (id == S_FX_DENOISE) { ++ filter->strength = strength; ++ int mode = obs_data_get_int(settings, S_MODE) == 1 ? 1 : 0; ++ if (filter->mode != mode) { ++ filter->mode = mode; ++ vfxErr = NvVFX_SetF32(filter->handle_blur, NVVFX_STRENGTH, (float)mode); ++ if (vfxErr == NVCV_SUCCESS && filter->images_allocated) ++ vfxErr = NvVFX_Load(filter->handle_blur); ++ if (vfxErr != NVCV_SUCCESS) ++ error("Error updating denoising FX %i", vfxErr); ++ } ++ if (strength == 0.f) ++ filter->denoise_reset = true; ++ } + if (id == S_FX_AIGS || id == S_FX_BG_BLUR) { + int mode = id == S_FX_BG_BLUR ? (int)S_MODE_PERF : (int)obs_data_get_int(settings, S_MODE); + if (filter->mode != mode) { +@@ -194,6 +239,8 @@ static void nvvfx_filter_actual_destroy(void *data) + NvVFX_DestroyEffect(filter->handle); + } + if (filter->handle_blur) { ++ if (filter->filter_id == S_FX_DENOISE && filter->stateObjectHandle) ++ NvVFX_DeallocateState(filter->handle_blur, filter->stateObjectHandle); + NvVFX_DestroyEffect(filter->handle_blur); + } + if (filter->stream) +@@ -233,6 +280,9 @@ static bool nvvfx_filter_create_internal(struct nvvfx_data *filter) + case S_FX_AIGS: + vfxErr = NvVFX_CreateEffect(NVVFX_FX_GREEN_SCREEN, &filter->handle); + break; ++ case S_FX_DENOISE: ++ vfxErr = NvVFX_CreateEffect(NVVFX_FX_DENOISING, &filter->handle_blur); ++ break; + case S_FX_BLUR: + vfxErr = NvVFX_CreateEffect(NVVFX_FX_BGBLUR, &filter->handle_blur); + break; +@@ -290,6 +340,18 @@ static bool nvvfx_filter_create_internal(struct nvvfx_data *filter) + if (NVCV_SUCCESS != vfxErr) + log_nverror_destroy(filter, vfxErr); + } ++ if (id == S_FX_DENOISE) { ++ char sdk[MAX_PATH], modelDir[MAX_PATH]; ++ if (!nvvfx_get_sdk_path(sdk, MAX_PATH)) ++ return false; ++ snprintf(modelDir, sizeof(modelDir), "%s\\models", sdk); ++ vfxErr = NvVFX_SetString(filter->handle_blur, NVVFX_MODEL_DIRECTORY, modelDir); ++ if (vfxErr != NVCV_SUCCESS) ++ return false; ++ vfxErr = NvVFX_SetCudaStream(filter->handle_blur, NVVFX_CUDA_STREAM, filter->stream); ++ if (vfxErr != NVCV_SUCCESS) ++ return false; ++ } + return true; + } + +@@ -358,7 +420,7 @@ static void *nvvfx_filter_create(obs_data_t *settings, obs_source_t *context, en + obs_leave_graphics(); + + /* 4. Allocate state for the AIGS & background blur */ +- if (nvvfx_new_sdk && id != S_FX_BLUR) { ++ if (nvvfx_new_sdk && (id == S_FX_AIGS || id == S_FX_BG_BLUR)) { + vfxErr = NvVFX_AllocateState(filter->handle, &filter->stateObjectHandle); + if (NVCV_SUCCESS != vfxErr) + return log_nverror_destroy(filter, vfxErr); +@@ -396,6 +458,11 @@ static void *nv_background_blur_filter_create(obs_data_t *settings, obs_source_t + return nvvfx_filter_create(settings, context, S_FX_BG_BLUR); + } + ++static void *nv_denoise_filter_create(obs_data_t *settings, obs_source_t *context) ++{ ++ return nvvfx_filter_create(settings, context, S_FX_DENOISE); ++} ++ + static void nvvfx_filter_reset(void *data, calldata_t *calldata) + { + struct nvvfx_data *filter = (struct nvvfx_data *)data; +@@ -410,6 +477,8 @@ static void nvvfx_filter_reset(void *data, calldata_t *calldata) + NvVFX_DestroyEffect(filter->handle); + } + if (filter->handle_blur) { ++ if (filter->filter_id == S_FX_DENOISE && filter->stateObjectHandle) ++ NvVFX_DeallocateState(filter->handle_blur, filter->stateObjectHandle); + NvVFX_DestroyEffect(filter->handle_blur); + } + if (filter->stream) { +@@ -423,7 +492,7 @@ static void nvvfx_filter_reset(void *data, calldata_t *calldata) + return; + + /* 3. load FX */ +- if (filter->filter_id != S_FX_BLUR) { ++ if (filter->filter_id == S_FX_AIGS || filter->filter_id == S_FX_BG_BLUR) { + vfxErr = NvVFX_SetU32(filter->handle, NVVFX_MODE, filter->mode); + if (NVCV_SUCCESS != vfxErr) + error("Error loading NVIDIA Video FX %i", vfxErr); +@@ -435,7 +504,7 @@ static void nvvfx_filter_reset(void *data, calldata_t *calldata) + error("Error loading NVIDIA Video FX %i", vfxErr); + } + if (filter->filter_id != S_FX_AIGS) { +- vfxErr = NvVFX_SetF32(filter->handle_blur, NVVFX_STRENGTH, filter->strength); ++ vfxErr = NvVFX_SetF32(filter->handle_blur, NVVFX_STRENGTH, filter->filter_id == S_FX_DENOISE ? (float)filter->mode : filter->strength); + if (NVCV_SUCCESS != vfxErr) + error("Error loading NVIDIA Video FX %i", vfxErr); + /* Newly recreated handles have no bound images yet. The next +@@ -535,9 +604,17 @@ static bool init_blur_images(struct nvvfx_data *filter) + uint32_t height = filter->height; + enum nvvfx_fx_id id = filter->filter_id; + ++ bool denoise = id == S_FX_DENOISE; ++ NvCVImage_ComponentType component = denoise ? NVCV_F32 : NVCV_U8; ++ unsigned char layout = denoise ? NVCV_PLANAR : NVCV_CHUNKY; + /* 1. Create and allocate Blur BGR NvCVimage (blur FX dst) */ +- NvCVImage_Create(width, height, NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_GPU, 1, &filter->blur_BGR_dst_img); +- NvCVImage_Alloc(filter->blur_BGR_dst_img, width, height, NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_GPU, 1); ++ NvCV_Status status; ++ if (filter->blur_BGR_dst_img) ++ status = NvCVImage_Realloc(filter->blur_BGR_dst_img, width, height, NVCV_BGR, component, layout, NVCV_GPU, 1); ++ else ++ status = NvCVImage_Create(width, height, NVCV_BGR, component, layout, NVCV_GPU, 1, &filter->blur_BGR_dst_img); ++ if (status != NVCV_SUCCESS) ++ return false; + + /* 2. Create dst NvCVImage */ + if (filter->RGBA_dst_img) { +@@ -549,13 +626,26 @@ static bool init_blur_images(struct nvvfx_data *filter) + + /* 3. Set input & output images for nv blur FX */ + NvVFX_SetImage(filter->handle_blur, NVVFX_INPUT_IMAGE, filter->BGR_src_img); +- if (id != S_FX_BLUR) ++ if (id == S_FX_BG_BLUR) + NvVFX_SetImage(filter->handle_blur, NVVFX_INPUT_IMAGE_1, filter->A_dst_img); +- else ++ else if (!denoise) + NvVFX_SetImage(filter->handle_blur, NVVFX_INPUT_IMAGE_1, NULL); + + NvVFX_SetImage(filter->handle_blur, NVVFX_OUTPUT_IMAGE, filter->blur_BGR_dst_img); + ++ if (denoise) { ++ if (filter->stateObjectHandle) ++ NvVFX_DeallocateState(filter->handle_blur, filter->stateObjectHandle); ++ filter->stateObjectHandle = NULL; ++ status = NvVFX_AllocateState(filter->handle_blur, &filter->stateObjectHandle); ++ if (status != NVCV_SUCCESS) { ++ error("Error allocating denoising temporal state %i", status); ++ return false; ++ } ++ status = NvVFX_SetStateObjectHandleArray(filter->handle_blur, NVVFX_STATE, &filter->stateObjectHandle); ++ if (status != NVCV_SUCCESS) ++ return false; ++ } + if (NvVFX_Load(filter->handle_blur) != NVCV_SUCCESS) { + error("Error loading blur FX"); + return false; +@@ -576,7 +666,7 @@ static void init_images(struct nvvfx_data *filter) + goto fail; + } + /* 2. Create blur texture & associated NvCVImage */ +- if (filter->filter_id == S_FX_BG_BLUR || filter->filter_id == S_FX_BLUR) { ++ if (filter->filter_id == S_FX_BG_BLUR || filter->filter_id == S_FX_BLUR || filter->filter_id == S_FX_DENOISE) { + if (!create_blur_texture(filter)) + goto fail; + } +@@ -584,12 +674,15 @@ static void init_images(struct nvvfx_data *filter) + if (!create_texrenders(filter)) + goto fail; + ++ bool denoise = filter->filter_id == S_FX_DENOISE; ++ NvCVImage_ComponentType component = denoise ? NVCV_F32 : NVCV_U8; ++ unsigned char layout = denoise ? NVCV_PLANAR : NVCV_CHUNKY; + /* 4. Create and allocate BGR NvCVImage (alpha mask FX src) */ + if (filter->BGR_src_img) { +- NvCVImage_Realloc(filter->BGR_src_img, width, height, NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_GPU, 1); ++ NvCVImage_Realloc(filter->BGR_src_img, width, height, NVCV_BGR, component, layout, NVCV_GPU, 1); + } else { +- NvCVImage_Create(width, height, NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_GPU, 1, &filter->BGR_src_img); +- NvCVImage_Alloc(filter->BGR_src_img, width, height, NVCV_BGR, NVCV_U8, NVCV_CHUNKY, NVCV_GPU, 1); ++ NvCVImage_Create(width, height, NVCV_BGR, component, layout, NVCV_GPU, 1, &filter->BGR_src_img); ++ NvCVImage_Alloc(filter->BGR_src_img, width, height, NVCV_BGR, component, layout, NVCV_GPU, 1); + } + + /* 5. Create and allocate Alpha NvCVimage (mask fx dst). */ +@@ -601,7 +694,7 @@ static void init_images(struct nvvfx_data *filter) + } + + /* 6. Init blur images */ +- if (filter->filter_id == S_FX_BLUR || filter->filter_id == S_FX_BG_BLUR) { ++ if (filter->filter_id == S_FX_BLUR || filter->filter_id == S_FX_BG_BLUR || filter->filter_id == S_FX_DENOISE) { + if (!init_blur_images(filter)) + goto fail; + } +@@ -643,7 +736,7 @@ static bool process_texture(struct nvvfx_data *filter) + } + + /* 2. Convert to BGR. */ +- vfxErr = NvCVImage_Transfer(filter->src_img, filter->BGR_src_img, 1.0f, process_stream, NULL); ++ vfxErr = NvCVImage_Transfer(filter->src_img, filter->BGR_src_img, id == S_FX_DENOISE ? 1.f / 255.f : 1.f, process_stream, NULL); + if (vfxErr != NVCV_SUCCESS) { + const char *errString = NvCV_GetErrorStringFromCode(vfxErr); + error("Error converting src to BGR img; error %i: %s", vfxErr, errString); +@@ -657,7 +750,7 @@ static bool process_texture(struct nvvfx_data *filter) + } + + /* 3. Run AIGS (AI Greenscreen) fx */ +- if (id != S_FX_BLUR) { ++ if (id == S_FX_AIGS || id == S_FX_BG_BLUR) { + vfxErr = NvVFX_Run(filter->handle, SYNC); + if (vfxErr != NVCV_SUCCESS) { + const char *errString = NvCV_GetErrorStringFromCode(vfxErr); +@@ -691,6 +784,12 @@ static bool process_texture(struct nvvfx_data *filter) + /* 5. BLUR FX */ + if (id != S_FX_AIGS) { + /* 5a. Run BLUR FX except for AIGS */ ++ if (id == S_FX_DENOISE && filter->denoise_reset) { ++ vfxErr = NvVFX_ResetState(filter->handle_blur, filter->stateObjectHandle); ++ if (vfxErr != NVCV_SUCCESS) ++ goto fail; ++ filter->denoise_reset = false; ++ } + vfxErr = NvVFX_Run(filter->handle_blur, SYNC); + if (vfxErr != NVCV_SUCCESS) { + const char *errString = NvCV_GetErrorStringFromCode(vfxErr); +@@ -699,7 +798,7 @@ static bool process_texture(struct nvvfx_data *filter) + nvvfx_filter_reset(filter, NULL); + } + /* 5b. Transfer blur result to an intermediate dst [RGBA, chunky, u8] */ +- vfxErr = NvCVImage_Transfer(filter->blur_BGR_dst_img, filter->RGBA_dst_img, 1.0f, filter->stream, NULL); ++ vfxErr = NvCVImage_Transfer(filter->blur_BGR_dst_img, filter->RGBA_dst_img, id == S_FX_DENOISE ? 255.f : 1.f, filter->stream, NULL); + if (vfxErr != NVCV_SUCCESS) { + error("Error transferring blurred to intermediate img [RGBA, chunky, u8], error %i, ", vfxErr); + goto fail; +@@ -760,6 +859,11 @@ static void nvvfx_filter_tick(void *data, float t) + const uint32_t cx = obs_source_get_base_width(target); + const uint32_t cy = obs_source_get_base_height(target); + ++ if (filter->filter_id == S_FX_DENOISE) { ++ filter->target_valid = cx >= 80 && cy >= 80 && cx <= 1920 && cy <= 1080; ++ if (!filter->target_valid) ++ return; // Unsupported resolutions bypass; never submit them to the SDK. ++ } + // initially the sizes are 0 + if (!cx || !cy) { + filter->target_valid = false; +@@ -767,7 +871,7 @@ static void nvvfx_filter_tick(void *data, float t) + } + + /* minimum size supported by SDK is (512,288) */ +- if (filter->filter_id != S_FX_BLUR) { ++ if (filter->filter_id == S_FX_AIGS || filter->filter_id == S_FX_BG_BLUR) { + filter->target_valid = cx >= 512 && cy >= 288; + if (!filter->target_valid) { + error("Size must be larger than (512,288)"); +@@ -840,6 +944,10 @@ static void draw_greenscreen_blur(struct nvvfx_data *filter, bool has_blur) + + if (obs_source_process_filter_begin_with_color_space(filter->context, format, source_space, + OBS_ALLOW_DIRECT_RENDERING)) { ++ gs_effect_set_float(gs_effect_get_param_by_name(filter->effect, "compare"), filter->compare ? 1.f : 0.f); ++ if (has_blur) ++ gs_effect_set_float(gs_effect_get_param_by_name(filter->effect, "processed_mix"), ++ filter->filter_id == S_FX_DENOISE ? filter->strength : 1.f); + gs_effect_set_texture(filter->mask_param, filter->alpha_texture); + if (has_blur) { + gs_effect_set_texture_srgb(filter->blur_param, filter->blur_texture); +@@ -905,7 +1013,7 @@ static void nvvfx_filter_render(void *data, gs_effect_t *effect, bool has_blur) + obs_source_get_color_space(target, OBS_COUNTOF(preferred_spaces), preferred_spaces); + + enum nvvfx_fx_id id = filter->filter_id; +- if (id == S_FX_BLUR && (source_space == GS_CS_709_EXTENDED || source_space == GS_CS_709_SCRGB)) { ++ if ((id == S_FX_BLUR || id == S_FX_DENOISE) && (source_space == GS_CS_709_EXTENDED || source_space == GS_CS_709_SCRGB)) { + obs_source_skip_video_filter(filter->context); + return; + } +@@ -1066,6 +1174,12 @@ static obs_properties_t *nvvfx_filter_properties(void *data) + { + struct nvvfx_data *filter = (struct nvvfx_data *)data; + obs_properties_t *props = obs_properties_create(); ++ obs_properties_add_bool(props, "compare", "Before / after split (native output)"); ++ if (filter->filter_id == S_FX_DENOISE) { ++ obs_property_t *mode = obs_properties_add_list(props, S_MODE, "Noise removal mode", OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); ++ obs_property_list_add_int(mode, "Preserve details", 0); ++ obs_property_list_add_int(mode, "Strong noise removal", 1); ++ } + if (filter->filter_id != S_FX_AIGS) { + obs_property_t *strength = + obs_properties_add_float_slider(props, S_STRENGTH, TEXT_MODE_BLUR_STRENGTH, 0, 1, 0.05); +@@ -1093,6 +1207,7 @@ static obs_properties_t *nvvfx_filter_properties(void *data) + + static void nvvfx_filter_defaults(obs_data_t *settings) + { ++ obs_data_set_default_bool(settings, "compare", false); + obs_data_set_default_int(settings, S_MODE, S_MODE_QUALITY); + obs_data_set_default_double(settings, S_THRESHOLDFX, S_THRESHOLDFX_DEFAULT); + obs_data_set_default_int(settings, S_PROCESSING, 1); +@@ -1282,3 +1397,19 @@ struct obs_source_info nvidia_background_blur_filter_info = { + .video_tick = nvvfx_filter_tick, + .video_get_color_space = nvvfx_filter_get_color_space, + }; ++ ++struct obs_source_info nvidia_denoise_filter_info = { ++ .id = "nv_denoise_filter", ++ .type = OBS_SOURCE_TYPE_FILTER, ++ .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB, ++ .get_name = nv_denoise_filter_name, ++ .create = nv_denoise_filter_create, ++ .destroy = nvvfx_filter_destroy, ++ .get_defaults = nvvfx_filter_defaults, ++ .get_properties = nvvfx_filter_properties, ++ .update = nvvfx_filter_update, ++ .filter_video = nvvfx_filter_video, ++ .video_render = nv_blur_filter_render, ++ .video_tick = nvvfx_filter_tick, ++ .video_get_color_space = nvvfx_filter_get_color_space, ++}; +-- +2.54.0.windows.1 + diff --git a/patches/0064-add-nvidia-ar-effects-and-demand-processing.patch b/patches/0064-add-nvidia-ar-effects-and-demand-processing.patch new file mode 100644 index 00000000..aaac4077 --- /dev/null +++ b/patches/0064-add-nvidia-ar-effects-and-demand-processing.patch @@ -0,0 +1,675 @@ +From 1246de1a4f0df3f12034dd6fdbdea75e4a8bddc1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?ClodoCap=C3=A9o?= + <159788250+ClodoCapeo@users.noreply.github.com> +Date: Sat, 10 Oct 2026 15:56:37 +0200 +Subject: [PATCH] feat(nvidia): AR camera effects and demand processing + +Bind installed Maxine AR face tracking and gaze redirection to the existing VFX GPU pipeline. Add bounded crop, native comparison, graphics-thread processing counters and lazy image allocation. Repair failure cleanup and partial allocations. Upstream core is unchanged; nv-filters integration is carried as a patch for the local capability audit. +--- + plugins/nv-filters/data/rtx_blur.effect | 9 +- + plugins/nv-filters/nv-filters.c | 13 + + plugins/nv-filters/nvidia-videofx-filter.c | 309 ++++++++++++++++++--- + 3 files changed, 288 insertions(+), 43 deletions(-) + +diff --git a/plugins/nv-filters/data/rtx_blur.effect b/plugins/nv-filters/data/rtx_blur.effect +index 89824b8c2..22f68ea9e 100644 +--- a/plugins/nv-filters/data/rtx_blur.effect ++++ b/plugins/nv-filters/data/rtx_blur.effect +@@ -5,6 +5,8 @@ uniform texture2d image; + uniform float multiplier; + uniform float compare; + uniform float processed_mix; ++uniform float2 crop_offset; ++uniform float crop_size; + uniform texture2d mask; + uniform texture2d blurred; + +@@ -55,10 +57,11 @@ FragPos VSConvertUnorm(uint id : VERTEXID) + + float4 Mask(FragData f_in) + { +- float4 rgba = image.Sample(texSampler, f_in.uv); +- float3 blur = srgb_nonlinear_to_linear(blurred.Sample(texSampler, f_in.uv).rgb); ++ float2 uv = (compare > 0.5 && f_in.uv.x < 0.5) ? f_in.uv : crop_offset + f_in.uv * crop_size; ++ float4 rgba = image.Sample(texSampler, uv); ++ float3 blur = srgb_nonlinear_to_linear(blurred.Sample(texSampler, uv).rgb); + // Between blurred and unblurred area we interpolate to have a smooth transition. +- float m = smoothstep(0.85, 0.95, mask.Sample(texSampler, f_in.uv).a); ++ float m = smoothstep(0.85, 0.95, mask.Sample(texSampler, uv).a); + float mix_amount = (compare > 0.5 && f_in.uv.x < 0.5) ? 0.0 : processed_mix; + float3 out_lin = lerp(rgba.rgb, lerp(blur, rgba.rgb, m), mix_amount); + return float4(out_lin, rgba.a); +diff --git a/plugins/nv-filters/nv-filters.c b/plugins/nv-filters/nv-filters.c +index 411f77ec6..0975ead04 100644 +--- a/plugins/nv-filters/nv-filters.c ++++ b/plugins/nv-filters/nv-filters.c +@@ -26,6 +26,12 @@ extern struct obs_source_info nvidia_blur_filter_info; + extern struct obs_source_info nvidia_background_blur_filter_info; + extern struct obs_source_info nvidia_denoise_filter_info; + extern bool nvvfx_denoise_models_available(void); ++extern struct obs_source_info nvidia_autoframe_filter_info; ++extern struct obs_source_info nvidia_eye_contact_filter_info; ++extern bool nvvfx_ar_load(void); ++extern bool nvvfx_ar_face_model_available(void); ++extern bool nvvfx_ar_eye_models_available(void); ++extern void nvvfx_ar_unload(void); + extern bool load_nvidia_vfx(void); + extern void unload_nvidia_vfx(void); + #endif +@@ -78,6 +84,12 @@ bool obs_module_load(void) + obs_register_source(&nvidia_background_blur_filter_info); + if (nvvfx_denoise_models_available()) + obs_register_source(&nvidia_denoise_filter_info); ++ if (nvvfx_ar_load()) { ++ if (nvvfx_ar_face_model_available()) ++ obs_register_source(&nvidia_autoframe_filter_info); ++ if (nvvfx_ar_eye_models_available()) ++ obs_register_source(&nvidia_eye_contact_filter_info); ++ } + } + #endif + return true; +@@ -89,6 +101,7 @@ void obs_module_unload(void) + unload_nvidia_afx(); + #endif + #ifdef LIBNVVFX_ENABLED ++ nvvfx_ar_unload(); + unload_nvidia_vfx(); + #endif + } +diff --git a/plugins/nv-filters/nvidia-videofx-filter.c b/plugins/nv-filters/nvidia-videofx-filter.c +index 60421f8a5..c1bc60fca 100644 +--- a/plugins/nv-filters/nvidia-videofx-filter.c ++++ b/plugins/nv-filters/nvidia-videofx-filter.c +@@ -4,6 +4,9 @@ + #include + #include + #include "nvvfx-load.h" ++#include ++#include ++#include + /* -------------------------------------------------------- */ + + #define do_log(level, format, ...) \ +@@ -45,7 +48,7 @@ + #define S_STRENGTH_DEFAULT 0.5 + #define TEXT_MODE_BLUR_STRENGTH MT_("Nvvfx.Method.Blur.Strength") + +-enum nvvfx_fx_id { S_FX_AIGS, S_FX_BLUR, S_FX_BG_BLUR, S_FX_DENOISE }; ++enum nvvfx_fx_id { S_FX_AIGS, S_FX_BLUR, S_FX_BG_BLUR, S_FX_DENOISE, S_FX_AUTOFRAME, S_FX_EYE }; + + bool nvvfx_loaded = false; + bool nvvfx_new_sdk = false; +@@ -86,6 +89,13 @@ struct nvvfx_data { + float threshold; + bool compare; + bool denoise_reset; ++ pulsar_ar_handle ar_handle; ++ pulsar_ar_rect ar_rects[25]; ++ pulsar_ar_boxes ar_boxes; ++ pulsar_ar_point ar_landmarks[126], ar_eye_landmarks[126]; ++ float ar_confidence[126], ar_gaze[2], ar_translation[3], ar_rotation[4], ar_direction[6]; ++ float crop_x, crop_y, crop_size, zoom, smoothing, frame_seconds, lost_seconds; ++ uint64_t processed_count, last_processing_ns; + + /* Every nth frame is processed through the FX (where n = + * processing_interval) so that the same mask is used for n consecutive +@@ -153,6 +163,92 @@ bool nvvfx_denoise_models_available(void) + return true; + } + ++static const char *nv_autoframe_filter_name(void *unused) ++{ ++ UNUSED_PARAMETER(unused); ++ return "NVIDIA Auto Frame"; ++} ++static const char *nv_eye_filter_name(void *unused) ++{ ++ UNUSED_PARAMETER(unused); ++ return "NVIDIA Eye Contact"; ++} ++ ++static bool nvvfx_ar_create(struct nvvfx_data *filter) ++{ ++ char models[MAX_PATH]; ++ bool eye = filter->filter_id == S_FX_EYE; ++ if (!pulsar_ar.module) ++ return false; ++#define AR_CHECK(call) do { NvCV_Status status = (call); if (status != NVCV_SUCCESS) { error("AR initialization failed: %i", status); return false; } } while (0) ++ AR_CHECK(pulsar_ar.create(eye ? "GazeRedirection" : "FaceDetection", &filter->ar_handle)); ++ snprintf(models, sizeof(models), "%s\\models", pulsar_ar.dir); ++ AR_CHECK(pulsar_ar.set_string(filter->ar_handle, "NvAR_Parameter_Config_ModelDir", models)); ++ AR_CHECK(pulsar_ar.set_stream(filter->ar_handle, "NvAR_Parameter_Config_CUDAStream", filter->stream)); ++ AR_CHECK(pulsar_ar.set_u32(filter->ar_handle, "NvAR_Parameter_Config_Temporal", 0xffffffffu)); ++ if (eye) { ++ AR_CHECK(pulsar_ar.set_u32(filter->ar_handle, "NvAR_Parameter_Config_Landmarks_Size", 126)); ++ AR_CHECK(pulsar_ar.set_u32(filter->ar_handle, "NvAR_Parameter_Config_GazeRedirect", 1)); ++ AR_CHECK(pulsar_ar.set_u32(filter->ar_handle, "NvAR_Parameter_Config_EyeSizeSensitivity", 3)); ++ } ++ AR_CHECK(pulsar_ar.load(filter->ar_handle)); ++#undef AR_CHECK ++ return true; ++} ++ ++static bool nvvfx_ar_images(struct nvvfx_data *filter) ++{ ++ bool eye = filter->filter_id == S_FX_EYE; ++#define AR_CHECK(call) do { NvCV_Status status = (call); if (status != NVCV_SUCCESS) { error("AR image binding failed: %i", status); return false; } } while (0) ++ AR_CHECK(pulsar_ar.set_object(filter->ar_handle, "NvAR_Parameter_Input_Image", filter->BGR_src_img, sizeof(NvCVImage))); ++ filter->ar_boxes.boxes = filter->ar_rects; ++ filter->ar_boxes.max_boxes = 25; ++ filter->ar_boxes.num_boxes = 0; ++ AR_CHECK(pulsar_ar.set_object(filter->ar_handle, "NvAR_Parameter_Output_BoundingBoxes", &filter->ar_boxes, sizeof(pulsar_ar_boxes))); ++ if (eye) { ++ AR_CHECK(pulsar_ar.set_object(filter->ar_handle, "NvAR_Parameter_Output_Image", filter->blur_BGR_dst_img, sizeof(NvCVImage))); ++ AR_CHECK(pulsar_ar.set_s32(filter->ar_handle, "NvAR_Parameter_Input_Width", (int)filter->width)); ++ AR_CHECK(pulsar_ar.set_s32(filter->ar_handle, "NvAR_Parameter_Input_Height", (int)filter->height)); ++ AR_CHECK(pulsar_ar.set_object(filter->ar_handle, "NvAR_Parameter_Output_Landmarks", filter->ar_landmarks, sizeof(pulsar_ar_point))); ++ AR_CHECK(pulsar_ar.set_object(filter->ar_handle, "NvAR_Parameter_Output_GazeOutputLandmarks", filter->ar_eye_landmarks, sizeof(pulsar_ar_point))); ++ AR_CHECK(pulsar_ar.set_f32_array(filter->ar_handle, "NvAR_Parameter_Output_LandmarksConfidence", filter->ar_confidence, 126)); ++ AR_CHECK(pulsar_ar.set_f32_array(filter->ar_handle, "NvAR_Parameter_Output_OutputGazeVector", filter->ar_gaze, 2)); ++ AR_CHECK(pulsar_ar.set_f32_array(filter->ar_handle, "NvAR_Parameter_Output_OutputHeadTranslation", filter->ar_translation, 3)); ++ AR_CHECK(pulsar_ar.set_object(filter->ar_handle, "NvAR_Parameter_Output_HeadPose", filter->ar_rotation, sizeof(filter->ar_rotation))); ++ AR_CHECK(pulsar_ar.set_object(filter->ar_handle, "NvAR_Parameter_Output_GazeDirection", filter->ar_direction, 3 * sizeof(float))); ++ } ++#undef AR_CHECK ++ return true; ++} ++ ++static void nvvfx_frame_subject(struct nvvfx_data *filter) ++{ ++ const pulsar_ar_rect *subject = NULL; ++ for (uint8_t i = 0; i < filter->ar_boxes.num_boxes && i < 25; ++i) { ++ const pulsar_ar_rect *box = &filter->ar_rects[i]; ++ if (isfinite(box->x) && isfinite(box->y) && isfinite(box->width) && isfinite(box->height) && ++ box->width > 0.f && box->height > 0.f && (!subject || box->width * box->height > subject->width * subject->height)) ++ subject = box; ++ } ++ float x = filter->crop_x, y = filter->crop_y, size = filter->crop_size; ++ if (subject) { ++ filter->lost_seconds = 0.f; ++ size = fminf(1.f, fmaxf(1.f / filter->zoom, fmaxf(subject->width / filter->width * 4.f, subject->height / filter->height * 4.f))); ++ x = fmaxf(0.f, fminf(1.f - size, (subject->x + subject->width * .5f) / filter->width - size * .5f)); ++ y = fmaxf(0.f, fminf(1.f - size, (subject->y + subject->height * 1.3f) / filter->height - size * .5f)); ++ } else { ++ filter->lost_seconds += filter->frame_seconds; ++ if (filter->lost_seconds > 1.f) { ++ x = y = 0.f; ++ size = 1.f; ++ } ++ } ++ float weight = 1.f - expf(-filter->frame_seconds / filter->smoothing); ++ filter->crop_x += (x - filter->crop_x) * weight; ++ filter->crop_y += (y - filter->crop_y) * weight; ++ filter->crop_size += (size - filter->crop_size) * weight; ++} ++ + static void nvvfx_filter_update(void *data, obs_data_t *settings) + { + struct nvvfx_data *filter = (struct nvvfx_data *)data; +@@ -163,8 +259,15 @@ static void nvvfx_filter_update(void *data, obs_data_t *settings) + filter->threshold = (float)obs_data_get_double(settings, S_THRESHOLDFX); + filter->processing_interval = (int)obs_data_get_int(settings, S_PROCESSING); + float strength = (float)obs_data_get_double(settings, S_STRENGTH); ++ if (id == S_FX_AUTOFRAME || id == S_FX_EYE) { ++ filter->strength = isfinite(strength) ? fmaxf(0.f, fminf(1.f, strength)) : 0.f; ++ float zoom = (float)obs_data_get_double(settings, "zoom"); ++ float smoothing = (float)obs_data_get_double(settings, "smoothing"); ++ filter->zoom = isfinite(zoom) ? fmaxf(1.f, fminf(3.f, zoom)) : 1.8f; ++ filter->smoothing = isfinite(smoothing) ? fmaxf(.05f, fminf(2.f, smoothing)) : .25f; ++ } + if (id == S_FX_DENOISE) { +- filter->strength = strength; ++ filter->strength = isfinite(strength) ? fmaxf(0.f, fminf(1.f, strength)) : 0.f; + int mode = obs_data_get_int(settings, S_MODE) == 1 ? 1 : 0; + if (filter->mode != mode) { + filter->mode = mode; +@@ -211,25 +314,25 @@ static void nvvfx_filter_actual_destroy(void *data) + + os_atomic_set_bool(&filter->processing_stop, true); + +- if (filter->images_allocated) { ++ /* Partial image allocation must be released too. Background blur owns ++ * both textures, not just the blurred one. */ ++ { + obs_enter_graphics(); +- if (filter->filter_id == S_FX_AIGS) ++ if (filter->alpha_texture) + gs_texture_destroy(filter->alpha_texture); +- else ++ if (filter->blur_texture) + gs_texture_destroy(filter->blur_texture); + + gs_texrender_destroy(filter->render); + gs_texrender_destroy(filter->render_unorm); + obs_leave_graphics(); +- NvCVImage_Destroy(filter->src_img); +- NvCVImage_Destroy(filter->BGR_src_img); +- NvCVImage_Destroy(filter->A_dst_img); +- NvCVImage_Destroy(filter->dst_img); +- if (filter->filter_id != S_FX_AIGS) { +- NvCVImage_Destroy(filter->blur_BGR_dst_img); +- NvCVImage_Destroy(filter->RGBA_dst_img); +- NvCVImage_Destroy(filter->blur_dst_img); +- } ++ if (filter->src_img) NvCVImage_Destroy(filter->src_img); ++ if (filter->BGR_src_img) NvCVImage_Destroy(filter->BGR_src_img); ++ if (filter->A_dst_img) NvCVImage_Destroy(filter->A_dst_img); ++ if (filter->dst_img) NvCVImage_Destroy(filter->dst_img); ++ if (filter->blur_BGR_dst_img) NvCVImage_Destroy(filter->blur_BGR_dst_img); ++ if (filter->RGBA_dst_img) NvCVImage_Destroy(filter->RGBA_dst_img); ++ if (filter->blur_dst_img) NvCVImage_Destroy(filter->blur_dst_img); + } + + if (filter->handle) { +@@ -243,6 +346,8 @@ static void nvvfx_filter_actual_destroy(void *data) + NvVFX_DeallocateState(filter->handle_blur, filter->stateObjectHandle); + NvVFX_DestroyEffect(filter->handle_blur); + } ++ if (filter->ar_handle) ++ pulsar_ar.destroy(filter->ar_handle); + if (filter->stream) + NvVFX_CudaStreamDestroy(filter->stream); + +@@ -280,6 +385,10 @@ static bool nvvfx_filter_create_internal(struct nvvfx_data *filter) + case S_FX_AIGS: + vfxErr = NvVFX_CreateEffect(NVVFX_FX_GREEN_SCREEN, &filter->handle); + break; ++ case S_FX_AUTOFRAME: ++ case S_FX_EYE: ++ vfxErr = NVCV_SUCCESS; ++ break; + case S_FX_DENOISE: + vfxErr = NvVFX_CreateEffect(NVVFX_FX_DENOISING, &filter->handle_blur); + break; +@@ -289,14 +398,14 @@ static bool nvvfx_filter_create_internal(struct nvvfx_data *filter) + case S_FX_BG_BLUR: + vfxErr = NvVFX_CreateEffect(NVVFX_FX_GREEN_SCREEN, &filter->handle); + if (NVCV_SUCCESS != vfxErr) +- log_nverror_destroy(filter, vfxErr); ++ return false; + vfxErr = NvVFX_CreateEffect(NVVFX_FX_BGBLUR, &filter->handle_blur); + break; + default: + return false; + } + if (NVCV_SUCCESS != vfxErr) +- log_nverror_destroy(filter, vfxErr); ++ return false; + /* debug */ + #ifdef _DEBUG + if (id == S_FX_AIGS || id == S_FX_BG_BLUR) { +@@ -313,7 +422,7 @@ static bool nvvfx_filter_create_internal(struct nvvfx_data *filter) + /* 2. Set models path & initialize CudaStream */ + vfxErr = NvVFX_CudaStreamCreate(&filter->stream); + if (NVCV_SUCCESS != vfxErr) +- log_nverror_destroy(filter, vfxErr); ++ return false; + + if (id == S_FX_AIGS || id == S_FX_BG_BLUR) { + char buffer[MAX_PATH]; +@@ -324,21 +433,22 @@ static bool nvvfx_filter_create_internal(struct nvvfx_data *filter) + * unvouched-for root -- NVVFX_MODEL_DIRECTORY is where the SDK + * goes looking for TensorRT packages to deserialise. */ + if (!nvvfx_get_sdk_path(buffer, MAX_PATH)) { +- log_nverror_destroy(filter, NVCV_ERR_MODEL); + return false; + } + size_t max_len = sizeof(modelDir) / sizeof(char); + snprintf(modelDir, max_len, "%s\\%s", buffer, PULSAR_NV_MODEL_SUBDIR); + vfxErr = NvVFX_SetString(filter->handle, NVVFX_MODEL_DIRECTORY, modelDir); ++ if (NVCV_SUCCESS != vfxErr) ++ return false; + vfxErr = NvVFX_SetCudaStream(filter->handle, NVVFX_CUDA_STREAM, filter->stream); + if (NVCV_SUCCESS != vfxErr) +- log_nverror_destroy(filter, vfxErr); ++ return false; + } + + if (id == S_FX_BLUR || id == S_FX_BG_BLUR) { + vfxErr = NvVFX_SetCudaStream(filter->handle_blur, NVVFX_CUDA_STREAM, filter->stream); + if (NVCV_SUCCESS != vfxErr) +- log_nverror_destroy(filter, vfxErr); ++ return false; + } + if (id == S_FX_DENOISE) { + char sdk[MAX_PATH], modelDir[MAX_PATH]; +@@ -352,6 +462,8 @@ static bool nvvfx_filter_create_internal(struct nvvfx_data *filter) + if (vfxErr != NVCV_SUCCESS) + return false; + } ++ if (id == S_FX_AUTOFRAME || id == S_FX_EYE) ++ return nvvfx_ar_create(filter); + return true; + } + +@@ -363,6 +475,23 @@ static void nvvfx_logger_callback(void *data, const char *msg) + blog(LOG_ERROR, "[NVIDIA Video Effect: '%s']", msg); + } + ++struct nvvfx_metrics_call { struct nvvfx_data *filter; calldata_t *result; }; ++static void nvvfx_metrics_graphics(void *data) ++{ ++ struct nvvfx_metrics_call *call = data; ++ struct nvvfx_data *filter = call->filter; ++ calldata_set_int(call->result, "processed_frames", filter->processed_count); ++ calldata_set_float(call->result, "last_processing_ms", filter->last_processing_ns / 1000000.0); ++ calldata_set_bool(call->result, "images_allocated", filter->images_allocated); ++ calldata_set_bool(call->result, "processing_stopped", filter->processing_stop); ++} ++static void nvvfx_metrics(void *data, calldata_t *result) ++{ ++ struct nvvfx_metrics_call call = { data, result }; ++ // Read only on the owning graphics thread while the caller holds the source. ++ obs_queue_task(OBS_TASK_GRAPHICS, nvvfx_metrics_graphics, &call, true); ++} ++ + static void *nvvfx_filter_create(obs_data_t *settings, obs_source_t *context, enum nvvfx_fx_id id) + { + struct nvvfx_data *filter = (struct nvvfx_data *)bzalloc(sizeof(*filter)); +@@ -386,6 +515,8 @@ static void *nvvfx_filter_create(obs_data_t *settings, obs_source_t *context, en + filter->filter_id = id; + // blur specific + filter->strength = -1; ++ filter->crop_size = 1.f; ++ filter->frame_seconds = 1.f / 60.f; + + #ifdef _DEBUG + /* sanity check of sdk version */ +@@ -399,8 +530,11 @@ static void *nvvfx_filter_create(obs_data_t *settings, obs_source_t *context, en + #endif + /* 1. Create FX */ + /* 2. Set models path & initialize CudaStream */ +- if (!nvvfx_filter_create_internal(filter)) ++ if (!nvvfx_filter_create_internal(filter)) { ++ error("Unable to initialize NVIDIA effect %i; releasing partial resources", id); ++ nvvfx_filter_destroy(filter); + return NULL; ++ } + + /* 3. Load effect. */ + char *effect_path = obs_module_file(id != S_FX_AIGS ? "rtx_blur.effect" : "rtx_greenscreen.effect"); +@@ -435,6 +569,7 @@ static void *nvvfx_filter_create(obs_data_t *settings, obs_source_t *context, en + } + + nvvfx_filter_update(filter, settings); ++ proc_handler_add(obs_source_get_proc_handler(context), "void get_nvidia_processing()", nvvfx_metrics, filter); + + /* Setup NVIDIA logger */ + if (nvvfx_new_sdk) +@@ -463,26 +598,41 @@ static void *nv_denoise_filter_create(obs_data_t *settings, obs_source_t *contex + return nvvfx_filter_create(settings, context, S_FX_DENOISE); + } + ++static void *nv_autoframe_filter_create(obs_data_t *settings, obs_source_t *context) ++{ ++ return nvvfx_filter_create(settings, context, S_FX_AUTOFRAME); ++} ++static void *nv_eye_filter_create(obs_data_t *settings, obs_source_t *context) ++{ ++ return nvvfx_filter_create(settings, context, S_FX_EYE); ++} ++ + static void nvvfx_filter_reset(void *data, calldata_t *calldata) + { + struct nvvfx_data *filter = (struct nvvfx_data *)data; + NvCV_Status vfxErr; + + os_atomic_set_bool(&filter->processing_stop, true); ++ if (filter->filter_id == S_FX_AUTOFRAME || filter->filter_id == S_FX_EYE) ++ return; + // [A] first destroy + if (filter->handle) { + if (filter->stateObjectHandle) { + NvVFX_DeallocateState(filter->handle, filter->stateObjectHandle); + } + NvVFX_DestroyEffect(filter->handle); ++ filter->handle = NULL; + } + if (filter->handle_blur) { + if (filter->filter_id == S_FX_DENOISE && filter->stateObjectHandle) + NvVFX_DeallocateState(filter->handle_blur, filter->stateObjectHandle); + NvVFX_DestroyEffect(filter->handle_blur); ++ filter->handle_blur = NULL; + } ++ filter->stateObjectHandle = NULL; + if (filter->stream) { + NvVFX_CudaStreamDestroy(filter->stream); ++ filter->stream = NULL; + } + + // [B] recreate +@@ -624,6 +774,8 @@ static bool init_blur_images(struct nvvfx_data *filter) + NvCVImage_Alloc(filter->RGBA_dst_img, width, height, NVCV_RGBA, NVCV_U8, NVCV_CHUNKY, NVCV_GPU, 1); + } + ++ if (id == S_FX_AUTOFRAME || id == S_FX_EYE) ++ return nvvfx_ar_images(filter); + /* 3. Set input & output images for nv blur FX */ + NvVFX_SetImage(filter->handle_blur, NVVFX_INPUT_IMAGE, filter->BGR_src_img); + if (id == S_FX_BG_BLUR) +@@ -666,7 +818,7 @@ static void init_images(struct nvvfx_data *filter) + goto fail; + } + /* 2. Create blur texture & associated NvCVImage */ +- if (filter->filter_id == S_FX_BG_BLUR || filter->filter_id == S_FX_BLUR || filter->filter_id == S_FX_DENOISE) { ++ if (filter->filter_id != S_FX_AIGS) { + if (!create_blur_texture(filter)) + goto fail; + } +@@ -694,7 +846,7 @@ static void init_images(struct nvvfx_data *filter) + } + + /* 6. Init blur images */ +- if (filter->filter_id == S_FX_BLUR || filter->filter_id == S_FX_BG_BLUR || filter->filter_id == S_FX_DENOISE) { ++ if (filter->filter_id != S_FX_AIGS) { + if (!init_blur_images(filter)) + goto fail; + } +@@ -724,6 +876,7 @@ fail: + enum nvvfx_run_mode { SYNC, ASYNC }; + static bool process_texture(struct nvvfx_data *filter) + { ++ const uint64_t started = os_gettime_ns(); + enum nvvfx_fx_id id = filter->filter_id; + CUstream process_stream = filter->stream; + +@@ -781,6 +934,20 @@ static bool process_texture(struct nvvfx_data *filter) + goto fail; + } + } ++ bool ar = id == S_FX_AUTOFRAME || id == S_FX_EYE; ++ if (ar) { ++ /* Keep a deterministic unchanged output when no face is detected. */ ++ vfxErr = NvCVImage_Transfer(filter->BGR_src_img, filter->blur_BGR_dst_img, 1.f, process_stream, NULL); ++ if (vfxErr != NVCV_SUCCESS) ++ goto fail; ++ vfxErr = pulsar_ar.run(filter->ar_handle); ++ if (vfxErr != NVCV_SUCCESS) { ++ error("Error running NVIDIA AR: %i", vfxErr); ++ goto fail; ++ } ++ if (id == S_FX_AUTOFRAME) ++ nvvfx_frame_subject(filter); ++ } + /* 5. BLUR FX */ + if (id != S_FX_AIGS) { + /* 5a. Run BLUR FX except for AIGS */ +@@ -790,7 +957,7 @@ static bool process_texture(struct nvvfx_data *filter) + goto fail; + filter->denoise_reset = false; + } +- vfxErr = NvVFX_Run(filter->handle_blur, SYNC); ++ vfxErr = ar ? NVCV_SUCCESS : NvVFX_Run(filter->handle_blur, SYNC); + if (vfxErr != NVCV_SUCCESS) { + const char *errString = NvCV_GetErrorStringFromCode(vfxErr); + error("Error running the BLUR FX; error %i: %s", vfxErr, errString); +@@ -827,6 +994,8 @@ static bool process_texture(struct nvvfx_data *filter) + } + } + ++ filter->processed_count++; ++ filter->last_processing_ns = os_gettime_ns() - started; + return true; + fail: + os_atomic_set_bool(&filter->processing_stop, true); +@@ -842,10 +1011,11 @@ static struct obs_source_frame *nvvfx_filter_video(void *data, struct obs_source + + static void nvvfx_filter_tick(void *data, float t) + { +- UNUSED_PARAMETER(t); ++ + + struct nvvfx_data *filter = (struct nvvfx_data *)data; + ++ filter->frame_seconds = fmaxf(.001f, fminf(.1f, t)); + if (filter->processing_stop) { + return; + } +@@ -884,12 +1054,8 @@ static void nvvfx_filter_tick(void *data, float t) + filter->width = cx; + filter->height = cy; + } +- if (!filter->images_allocated) { +- obs_enter_graphics(); +- init_images(filter); +- obs_leave_graphics(); +- filter->initial_render = false; +- } ++ /* Allocate on render demand. An unused source must not allocate images ++ * or load its video model just because it receives ticks. */ + + filter->processed_frame = false; + } +@@ -947,7 +1113,15 @@ static void draw_greenscreen_blur(struct nvvfx_data *filter, bool has_blur) + gs_effect_set_float(gs_effect_get_param_by_name(filter->effect, "compare"), filter->compare ? 1.f : 0.f); + if (has_blur) + gs_effect_set_float(gs_effect_get_param_by_name(filter->effect, "processed_mix"), +- filter->filter_id == S_FX_DENOISE ? filter->strength : 1.f); ++ filter->filter_id == S_FX_DENOISE || filter->filter_id == S_FX_EYE ? filter->strength : 1.f); ++ if (has_blur) { ++ struct vec2 crop; ++ vec2_set(&crop, filter->filter_id == S_FX_AUTOFRAME ? filter->crop_x * filter->strength : 0.f, ++ filter->filter_id == S_FX_AUTOFRAME ? filter->crop_y * filter->strength : 0.f); ++ gs_effect_set_vec2(gs_effect_get_param_by_name(filter->effect, "crop_offset"), &crop); ++ gs_effect_set_float(gs_effect_get_param_by_name(filter->effect, "crop_size"), ++ filter->filter_id == S_FX_AUTOFRAME ? 1.f + (filter->crop_size - 1.f) * filter->strength : 1.f); ++ } + gs_effect_set_texture(filter->mask_param, filter->alpha_texture); + if (has_blur) { + gs_effect_set_texture_srgb(filter->blur_param, filter->blur_texture); +@@ -991,12 +1165,6 @@ static void nvvfx_filter_render(void *data, gs_effect_t *effect, bool has_blur) + return; + } + +- /* 1. Render to retrieve texture. */ +- if (!filter->render) { +- obs_source_skip_video_filter(filter->context); +- return; +- } +- + const uint32_t target_flags = obs_source_get_output_flags(target); + const uint32_t parent_flags = obs_source_get_output_flags(parent); + +@@ -1013,17 +1181,22 @@ static void nvvfx_filter_render(void *data, gs_effect_t *effect, bool has_blur) + obs_source_get_color_space(target, OBS_COUNTOF(preferred_spaces), preferred_spaces); + + enum nvvfx_fx_id id = filter->filter_id; +- if ((id == S_FX_BLUR || id == S_FX_DENOISE) && (source_space == GS_CS_709_EXTENDED || source_space == GS_CS_709_SCRGB)) { ++ if ((id == S_FX_BLUR || id == S_FX_DENOISE || id == S_FX_EYE || id == S_FX_AUTOFRAME) && (source_space == GS_CS_709_EXTENDED || source_space == GS_CS_709_SCRGB)) { + obs_source_skip_video_filter(filter->context); + return; + } + +- if (filter->space != source_space) { ++ if (!filter->images_allocated || filter->space != source_space) { + filter->space = source_space; + init_images(filter); + filter->initial_render = false; ++ if (!filter->images_allocated || filter->processing_stop) { ++ obs_source_skip_video_filter(filter->context); ++ return; ++ } + } + ++ /* Texrenders are created by demand allocation above. */ + gs_texrender_t *const render = filter->render; + gs_texrender_reset(render); + gs_blend_state_push(); +@@ -1175,6 +1348,10 @@ static obs_properties_t *nvvfx_filter_properties(void *data) + struct nvvfx_data *filter = (struct nvvfx_data *)data; + obs_properties_t *props = obs_properties_create(); + obs_properties_add_bool(props, "compare", "Before / after split (native output)"); ++ if (filter->filter_id == S_FX_AUTOFRAME) { ++ obs_properties_add_float_slider(props, "zoom", "Maximum zoom", 1, 3, .05); ++ obs_properties_add_float_slider(props, "smoothing", "Follow smoothing (seconds)", .05, 2, .05); ++ } + if (filter->filter_id == S_FX_DENOISE) { + obs_property_t *mode = obs_properties_add_list(props, S_MODE, "Noise removal mode", OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(mode, "Preserve details", 0); +@@ -1207,6 +1384,8 @@ static obs_properties_t *nvvfx_filter_properties(void *data) + + static void nvvfx_filter_defaults(obs_data_t *settings) + { ++ obs_data_set_default_double(settings, "zoom", 1.8); ++ obs_data_set_default_double(settings, "smoothing", .25); + obs_data_set_default_bool(settings, "compare", false); + obs_data_set_default_int(settings, S_MODE, S_MODE_QUALITY); + obs_data_set_default_double(settings, S_THRESHOLDFX, S_THRESHOLDFX_DEFAULT); +@@ -1413,3 +1592,53 @@ struct obs_source_info nvidia_denoise_filter_info = { + .video_tick = nvvfx_filter_tick, + .video_get_color_space = nvvfx_filter_get_color_space, + }; ++ ++bool nvvfx_ar_load(void) ++{ ++ return pulsar_ar_load(); ++} ++bool nvvfx_ar_face_model_available(void) ++{ ++ return pulsar_ar_has_model("face_detection_*.trtpkg"); ++} ++bool nvvfx_ar_eye_models_available(void) ++{ ++ return nvvfx_ar_face_model_available() && pulsar_ar_has_model("faceland_126_*.trtpkg") && ++ pulsar_ar_has_model("gazeredir_encoder_*.trtpkg") && pulsar_ar_has_model("gazeredir_decoder_*.trtpkg"); ++} ++void nvvfx_ar_unload(void) ++{ ++ pulsar_ar_unload(); ++} ++ ++struct obs_source_info nvidia_autoframe_filter_info = { ++ .id = "nv_autoframe_filter", ++ .type = OBS_SOURCE_TYPE_FILTER, ++ .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB, ++ .get_name = nv_autoframe_filter_name, ++ .create = nv_autoframe_filter_create, ++ .destroy = nvvfx_filter_destroy, ++ .get_defaults = nvvfx_filter_defaults, ++ .get_properties = nvvfx_filter_properties, ++ .update = nvvfx_filter_update, ++ .filter_video = nvvfx_filter_video, ++ .video_render = nv_blur_filter_render, ++ .video_tick = nvvfx_filter_tick, ++ .video_get_color_space = nvvfx_filter_get_color_space, ++}; ++ ++struct obs_source_info nvidia_eye_contact_filter_info = { ++ .id = "nv_eye_contact_filter", ++ .type = OBS_SOURCE_TYPE_FILTER, ++ .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_SRGB, ++ .get_name = nv_eye_filter_name, ++ .create = nv_eye_filter_create, ++ .destroy = nvvfx_filter_destroy, ++ .get_defaults = nvvfx_filter_defaults, ++ .get_properties = nvvfx_filter_properties, ++ .update = nvvfx_filter_update, ++ .filter_video = nvvfx_filter_video, ++ .video_render = nv_blur_filter_render, ++ .video_tick = nvvfx_filter_tick, ++ .video_get_color_space = nvvfx_filter_get_color_space, ++}; +-- +2.54.0.windows.1 + diff --git a/patches/README.md b/patches/README.md index a8297ecc..2edde604 100644 --- a/patches/README.md +++ b/patches/README.md @@ -1,6 +1,6 @@ # Pulsar OBS patch stack -Pulsar 3.0.0 carries **59 patches** above the pinned +Pulsar 3.0.0 carries **61 patches** above the pinned `ZabLaboratory/obs-studio` revision `bd73b922891e56839b0bc86bdc519802802f9d68`. Five foundational changes are already in that fork revision. @@ -86,6 +86,12 @@ loop and does not validate every plugin or the complete release package. ## Removing or replacing a patch +Patch 0063 adds native VFX video denoising and same-frame comparison. Patch 0064 +adds AR face following and eye contact, demand-driven image allocation and +read-only processing counters. Both use the existing GPU source texture, with +no second capture producer. See the complete change reference for SDK gates, +lifecycle repairs, shader semantics and qualification limits. + Only remove a patch after proving that its behavior is supplied by the new pin or an explicitly approved replacement. Replay and validate the **whole dependent stack**. Never delete an old patch from the middle as an operational diff --git a/plugins/pulsar-multi-stream/README.md b/plugins/pulsar-multi-stream/README.md index 81319aa0..47313680 100644 --- a/plugins/pulsar-multi-stream/README.md +++ b/plugins/pulsar-multi-stream/README.md @@ -30,6 +30,8 @@ goes through the vendor namespace. | Request | Inputs | Outputs | |---|---|---| +| `GetNvidiaGpuUsage` | — | `available, scope: "whole-device", reason, devices` with optional measured GPU/VRAM counters; read-only driver query, no per-effect attribution. | +| `GetNativeEffectMetrics` | `sourceName, filterName` | `available, processedFrames, lastProcessingMs, imagesAllocated, processingStopped`; native graphics-thread counters, pipeline wall-time scope. Unknown sources/unsupported filters return unavailable; no filter is created. | | `GetDestinations` | — | `destinations: [{id, name, kind, url, enabled, active}, ...]` | | `CreateDestination` | `name, kind ("rtmp_custom" \| "vod_local" \| "twitch" \| "youtube"), url, key?` | `id` (or `error`) | | `RemoveDestination` | `id` | `removed: bool` | diff --git a/plugins/pulsar-multi-stream/src/plugin-main.cpp b/plugins/pulsar-multi-stream/src/plugin-main.cpp index c48e9983..e46c94d0 100644 --- a/plugins/pulsar-multi-stream/src/plugin-main.cpp +++ b/plugins/pulsar-multi-stream/src/plugin-main.cpp @@ -38,6 +38,7 @@ #include #include #include +#include // #167 -- the SAME probe the nv-filters module gates itself on, so the // manifest publishes the load decision rather than a guess at it. It is the // first thing here that pulls in , hence NOMINMAX: this file uses @@ -82,6 +83,35 @@ OBS_DECLARE_MODULE() OBS_MODULE_AUTHOR("Pulsar") OBS_MODULE_USE_DEFAULT_LOCALE("pulsar-multi-stream", "en-US") +#include +static void on_get_nvidia_gpu_usage(obs_data_t *, obs_data_t *result, void *) +{ + pulsar_nvidia_gpu_usage(result); +} +static void on_get_native_effect_metrics(obs_data_t *request, obs_data_t *result, void *) +{ + obs_data_set_bool(result, "available", false); + obs_source_t *source = obs_get_source_by_name(obs_data_get_string(request, "sourceName")); + if (!source) return; + obs_source_t *filter = obs_source_get_filter_by_name(source, obs_data_get_string(request, "filterName")); + if (filter) { + calldata_t data; + calldata_init(&data); + const bool available = proc_handler_call(obs_source_get_proc_handler(filter), "get_nvidia_processing", &data); + obs_data_set_bool(result, "available", available); + if (available) { + obs_data_set_int(result, "processedFrames", calldata_int(&data, "processed_frames")); + obs_data_set_double(result, "lastProcessingMs", calldata_float(&data, "last_processing_ms")); + obs_data_set_bool(result, "imagesAllocated", calldata_bool(&data, "images_allocated")); + obs_data_set_bool(result, "processingStopped", calldata_bool(&data, "processing_stopped")); + obs_data_set_string(result, "scope", "native-pipeline-wall-time"); + } + calldata_free(&data); + obs_source_release(filter); + } + obs_source_release(source); +} + const char *obs_module_name(void) { return "pulsar-multi-stream"; } const char *obs_module_description(void) { return "First-class multi-destination streaming for Pulsar"; } @@ -2735,10 +2765,17 @@ void on_get_capabilities(obs_data_t * /*req*/, obs_data_t *res, void *) OBSDataAutoRelease entry = obs_data_create(); obs_data_set_string(entry, "applicability", kRegimeReadOnly); obs_data_set_bool(entry, "module_loaded", registered); + auto *nv_module = obs_get_module("nv-filters"); + using NvFeatureFlags = uint32_t (*)(void); + auto flags = nv_module ? reinterpret_cast( + os_dlsym(obs_get_module_lib(nv_module), "pulsar_nv_feature_flags")) : nullptr; + obs_data_set_bool(entry, "same_frame_comparison", registered && flags && (flags() & 1u)); OBSDataAutoRelease afx = sdk_entry(probe.afx); OBSDataAutoRelease vfx = sdk_entry(probe.vfx); + OBSDataAutoRelease ar = sdk_entry(probe.ar); obs_data_set_obj(entry, "afx", afx); obs_data_set_obj(entry, "vfx", vfx); + obs_data_set_obj(entry, "ar", ar); obs_data_set_obj(caps, "nv_filters", entry); } @@ -3235,6 +3272,8 @@ void obs_module_post_load(void) obs_websocket_vendor_register_request(g_vendor, "GetVideoSettings", on_get_video_settings, nullptr); obs_websocket_vendor_register_request(g_vendor, "SetVideoSettings", on_set_video_settings, nullptr); obs_websocket_vendor_register_request(g_vendor, "GetCapabilities", on_get_capabilities, nullptr); + obs_websocket_vendor_register_request(g_vendor, "GetNvidiaGpuUsage", on_get_nvidia_gpu_usage, nullptr); + obs_websocket_vendor_register_request(g_vendor, "GetNativeEffectMetrics", on_get_native_effect_metrics, nullptr); obs_websocket_vendor_register_request(g_vendor, "GetAudioTracks", on_get_audio_tracks, nullptr); obs_websocket_vendor_register_request(g_vendor, "MeasureAudioTrackFlow", on_measure_audio_track_flow, nullptr); obs_websocket_vendor_register_request(g_vendor, "GetProgramAudioRoute", on_get_program_audio_route, nullptr); diff --git a/plugins/pulsar-nv-secure-load/README.md b/plugins/pulsar-nv-secure-load/README.md index cee411fc..37ac1587 100644 --- a/plugins/pulsar-nv-secure-load/README.md +++ b/plugins/pulsar-nv-secure-load/README.md @@ -1,6 +1,6 @@ # pulsar-nv-secure-load -Header-only. One header, `pulsar-nv-secure-load.h`, holding the whole of +Header-only. `pulsar-nv-secure-load.h` owns the common policy, with a minimal AR ABI adapter and a read-only NVML driver query. It holds Pulsar's answer to *where may an NVIDIA Maxine SDK DLL or model come from, and is one there right now*. @@ -76,6 +76,23 @@ loader. See [architecture](../../docs/ARCHITECTURE.md). ## Installed SDK regression +AR resolves only the system Known Folder Program Files path +`NVIDIA Corporation/NVIDIA AR SDK`, never an environment or renderer override. +Root and models share the non-writable, non-reparse policy. Registration also +requires the specific face-detection/landmark/gaze model files. Missing AR does +not disable independent AFX/VFX filters. The AR adapter reuses VFX NvCV interop. + +`pulsar-nvidia-gpu.h` samples NVML loaded from System32 only. It reports the +whole physical GPU, including other applications, and balances its own +reference-counted init/shutdown. Failed measurements remain absent; no SDK +binary or model is distributed. This is not per-effect GPU attribution. + +`probe-nvidia-video-parity.py` checks both denoise models, alpha preservation, +live blend and four same-frame VFX splits. `probe-nvidia-ar-parity.py` takes +`--portrait`, checks face translation, no-face fallback, gaze correction, +cold allocation, idle inference counts and resumption. No physical camera or +microphone is selected by these tests. + [`scripts/probe-nvidia-effects.py`](../../scripts/probe-nvidia-effects.py) is an explicit hardware test for a machine with the official compatible AFX/VFX redistributables installed. It takes `--exe`, `--work`, `--report`, `--afx` and diff --git a/plugins/pulsar-nv-secure-load/pulsar-nv-secure-load.h b/plugins/pulsar-nv-secure-load/pulsar-nv-secure-load.h index b7218f7c..6996caab 100644 --- a/plugins/pulsar-nv-secure-load/pulsar-nv-secure-load.h +++ b/plugins/pulsar-nv-secure-load/pulsar-nv-secure-load.h @@ -157,6 +157,7 @@ struct pulsar_nv_sdk_probe { struct pulsar_nv_probe_result { struct pulsar_nv_sdk_probe afx; struct pulsar_nv_sdk_probe vfx; + struct pulsar_nv_sdk_probe ar; }; /* ---- path validation -------------------------------------------------- @@ -583,12 +584,34 @@ static void pulsar_nv_probe_vfx(struct pulsar_nv_sdk_probe *p) p->usable = p->dir_valid && p->dlls_present && p->models_present && p->version_ok; } +/* AR has a fixed system-known installation root, never an inherited override. */ +static void pulsar_nv_probe_ar(struct pulsar_nv_sdk_probe *p) +{ + char progfiles[MAX_PATH], candidate[MAX_PATH]; + memset(p, 0, sizeof(*p)); + p->min_version = 0x00080700u; + if (!pulsar_nv_program_files(progfiles, sizeof(progfiles))) + return; + if (snprintf(candidate, sizeof(candidate), "%s\\NVIDIA Corporation\\NVIDIA AR SDK", progfiles) <= 0) + return; + if (!pulsar_nv_validate_dir(candidate, p->dir, MAX_PATH)) + return; + p->dir_valid = true; + p->dlls_present = pulsar_nv_file_exists(p->dir, NULL, "nvARPose.dll") && + pulsar_nv_file_exists(p->dir, NULL, PULSAR_NV_CVIMAGE_DLL); + p->models_present = pulsar_nv_subdir_ok(p->dir, PULSAR_NV_MODEL_SUBDIR); + p->version_readable = pulsar_nv_read_version(p->dir, "nvARPose.dll", &p->version); + p->version_ok = p->version_readable && p->version >= p->min_version; + p->usable = p->dir_valid && p->dlls_present && p->models_present && p->version_ok; +} + static void pulsar_nv_probe(struct pulsar_nv_probe_result *out) { if (!out) return; pulsar_nv_probe_afx(&out->afx); pulsar_nv_probe_vfx(&out->vfx); + pulsar_nv_probe_ar(&out->ar); } /* The module gate. False here means obs_module_load() refuses, so neither diff --git a/plugins/pulsar-nv-secure-load/pulsar-nvar-load.h b/plugins/pulsar-nv-secure-load/pulsar-nvar-load.h new file mode 100644 index 00000000..a5271693 --- /dev/null +++ b/plugins/pulsar-nv-secure-load/pulsar-nvar-load.h @@ -0,0 +1,84 @@ +#pragma once +/* Minimal runtime ABI adapter for NVIDIA Maxine AR 0.8.7. + * Public API reference: NVIDIA-Maxine/Maxine-AR-SDK/nvar/include/nvAR.h. + * Included after nvvfx-load.h; reuses its NvCVImage ABI and CUDA interop. + * No SDK binary, model, path override or second capture producer is shipped. + */ +#include "pulsar-nv-secure-load.h" + +typedef struct pulsar_ar_rect { float x, y, width, height; } pulsar_ar_rect; +typedef struct pulsar_ar_boxes { + pulsar_ar_rect *boxes; + uint8_t num_boxes; + uint8_t max_boxes; +} pulsar_ar_boxes; +typedef struct pulsar_ar_point { float x, y; } pulsar_ar_point; +typedef void *pulsar_ar_handle; + +struct pulsar_ar_api { + HMODULE module; + char dir[MAX_PATH]; + NvCV_Status (*create)(const char *, pulsar_ar_handle *); + NvCV_Status (*load)(pulsar_ar_handle); + NvCV_Status (*run)(pulsar_ar_handle); + NvCV_Status (*destroy)(pulsar_ar_handle); + NvCV_Status (*set_u32)(pulsar_ar_handle, const char *, unsigned int); + NvCV_Status (*set_s32)(pulsar_ar_handle, const char *, int); + NvCV_Status (*set_object)(pulsar_ar_handle, const char *, void *, unsigned long); + NvCV_Status (*set_string)(pulsar_ar_handle, const char *, const char *); + NvCV_Status (*set_stream)(pulsar_ar_handle, const char *, CUstream); + NvCV_Status (*set_f32_array)(pulsar_ar_handle, const char *, float *, int); +}; +static struct pulsar_ar_api pulsar_ar; + +static void pulsar_ar_unload(void) +{ + if (pulsar_ar.module) + FreeLibrary(pulsar_ar.module); + memset(&pulsar_ar, 0, sizeof(pulsar_ar)); +} + +static bool pulsar_ar_has_model(const char *pattern) +{ + char path[MAX_PATH]; + WIN32_FIND_DATAA item; + snprintf(path, sizeof(path), "%s\\models\\%s", pulsar_ar.dir, pattern); + HANDLE search = FindFirstFileA(path, &item); + if (search == INVALID_HANDLE_VALUE) + return false; + bool valid = !(item.dwFileAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)) && + (item.nFileSizeHigh || item.nFileSizeLow); + FindClose(search); + return valid; +} + +static bool pulsar_ar_load(void) +{ + struct pulsar_nv_sdk_probe probe; + pulsar_nv_probe_ar(&probe); + if (!probe.usable) + return false; + memcpy(pulsar_ar.dir, probe.dir, sizeof(pulsar_ar.dir)); + /* The common confinement helper uses DLL_LOAD_DIR | SYSTEM32 only. */ + pulsar_ar.module = pulsar_nv_load_from_dir(probe.dir, "nvARPose.dll"); + if (!pulsar_ar.module) + return false; +#define AR_SYMBOL(field, exported) \ + *(FARPROC *)&pulsar_ar.field = GetProcAddress(pulsar_ar.module, exported); \ + if (!pulsar_ar.field) goto fail + AR_SYMBOL(create, "NvAR_Create"); + AR_SYMBOL(load, "NvAR_Load"); + AR_SYMBOL(run, "NvAR_Run"); + AR_SYMBOL(destroy, "NvAR_Destroy"); + AR_SYMBOL(set_u32, "NvAR_SetU32"); + AR_SYMBOL(set_s32, "NvAR_SetS32"); + AR_SYMBOL(set_object, "NvAR_SetObject"); + AR_SYMBOL(set_string, "NvAR_SetString"); + AR_SYMBOL(set_stream, "NvAR_SetCudaStream"); + AR_SYMBOL(set_f32_array, "NvAR_SetF32Array"); +#undef AR_SYMBOL + return true; +fail: + pulsar_ar_unload(); + return false; +} diff --git a/plugins/pulsar-nv-secure-load/pulsar-nvidia-gpu.h b/plugins/pulsar-nv-secure-load/pulsar-nvidia-gpu.h new file mode 100644 index 00000000..4bba96c9 --- /dev/null +++ b/plugins/pulsar-nv-secure-load/pulsar-nvidia-gpu.h @@ -0,0 +1,69 @@ +#pragma once +/* Read-only NVML sampling of physical GPUs, not per-effect attribution. + * ABI: https://docs.nvidia.com/deploy/nvml-api/latest/api/group__nvmlDeviceQueries.html + * This driver library is loaded from System32 only and is never bundled. */ +static void pulsar_nvidia_gpu_usage(obs_data_t *result) +{ + obs_data_set_bool(result, "available", false); + obs_data_set_string(result, "scope", "whole-device"); + obs_data_set_string(result, "reason", "driver-unavailable"); +#ifdef _WIN32 + HMODULE driver = LoadLibraryExW(L"nvml.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!driver) return; + using Device = void *; + struct Utilization { unsigned int gpu, memory; }; + struct Memory { unsigned long long total, free, used; }; + auto init = reinterpret_cast(GetProcAddress(driver, "nvmlInit_v2")); + auto shutdown = reinterpret_cast(GetProcAddress(driver, "nvmlShutdown")); + auto count_fn = reinterpret_cast(GetProcAddress(driver, "nvmlDeviceGetCount_v2")); + auto handle_fn = reinterpret_cast(GetProcAddress(driver, "nvmlDeviceGetHandleByIndex_v2")); + auto name_fn = reinterpret_cast(GetProcAddress(driver, "nvmlDeviceGetName")); + auto uuid_fn = reinterpret_cast(GetProcAddress(driver, "nvmlDeviceGetUUID")); + auto utilization_fn = reinterpret_cast(GetProcAddress(driver, "nvmlDeviceGetUtilizationRates")); + auto memory_fn = reinterpret_cast(GetProcAddress(driver, "nvmlDeviceGetMemoryInfo")); + if (!init || !shutdown || !count_fn || !handle_fn || !name_fn || !uuid_fn || !utilization_fn || !memory_fn) { + obs_data_set_string(result, "reason", "driver-api-unavailable"); + FreeLibrary(driver); + return; + } + const int status = init(); + if (status != 0) { + obs_data_set_string(result, "reason", "driver-query-failed"); + obs_data_set_int(result, "nativeStatus", status); + FreeLibrary(driver); + return; + } + unsigned int count = 0; + const int count_status = count_fn(&count); + OBSDataArrayAutoRelease devices = obs_data_array_create(); + if (count_status == 0 && count <= 32) { + for (unsigned int index = 0; index < count; index++) { + Device device = nullptr; + if (handle_fn(index, &device) != 0) continue; + char name[128] = {}, uuid[128] = {}; + if (name_fn(device, name, sizeof(name)) != 0 || uuid_fn(device, uuid, sizeof(uuid)) != 0) continue; + OBSDataAutoRelease entry = obs_data_create(); + obs_data_set_int(entry, "index", index); + obs_data_set_string(entry, "name", name); + obs_data_set_string(entry, "uuid", uuid); + Utilization utilization = {}; + if (utilization_fn(device, &utilization) == 0 && utilization.gpu <= 100 && utilization.memory <= 100) { + obs_data_set_int(entry, "gpuPercent", utilization.gpu); + obs_data_set_int(entry, "memoryPercent", utilization.memory); + } + Memory memory = {}; + if (memory_fn(device, &memory) == 0) { + obs_data_set_int(entry, "usedMemoryBytes", static_cast(memory.used)); + obs_data_set_int(entry, "totalMemoryBytes", static_cast(memory.total)); + } + obs_data_array_push_back(devices, entry); + } + } + const bool available = obs_data_array_count(devices) > 0; + obs_data_set_array(result, "devices", devices); + obs_data_set_bool(result, "available", available); + obs_data_set_string(result, "reason", available ? "available" : "driver-query-failed"); + shutdown(); // Balance only this request's reference-counted initialization. + FreeLibrary(driver); +#endif +} diff --git a/scripts/check-nv-filters-packaging.py b/scripts/check-nv-filters-packaging.py index a9ce5eb2..755e033b 100644 --- a/scripts/check-nv-filters-packaging.py +++ b/scripts/check-nv-filters-packaging.py @@ -41,6 +41,7 @@ NVIDIA_SDK_DLLS = ( "NVAudioEffects.dll", "NVVideoEffects.dll", + "nvARPose.dll", "NVCVImage.dll", "nvcuda.dll", ) diff --git a/scripts/probe-nvidia-ar-parity.py b/scripts/probe-nvidia-ar-parity.py new file mode 100644 index 00000000..8bcbd6e3 --- /dev/null +++ b/scripts/probe-nvidia-ar-parity.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Native AR regression using an explicit --portrait fixture, never a camera. + +The supplied portrait is resized/translated for tracking tests. Proof separates +same-frame pixels, lack-of-face fallback, live intensity and native render time. +""" +import argparse +import asyncio +import base64 +import hashlib +import importlib.util +import io +import json +from pathlib import Path +import sys +import time + +import numpy as np +from PIL import Image +import websockets + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location("installed_ar_probe", ROOT / "scripts/probe-nvidia-effects.py") +base = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = base +spec.loader.exec_module(base) +portrait_arg = argparse.ArgumentParser(add_help=False) +portrait_arg.add_argument("--portrait", type=Path, required=True) +portrait_options, remaining = portrait_arg.parse_known_args() +sys.argv = [sys.argv[0], *remaining] + +async def exercise(proc, work, report, record_video=False): + report["fixture"] = {"path": str(portrait_options.portrait.resolve()), "sha256": hashlib.sha256(portrait_options.portrait.read_bytes()).hexdigest()} + portrait = Image.open(portrait_options.portrait).convert("RGB") + for name, x in [("left", 120), ("right", 1000)]: + canvas = Image.new("RGB", (1920, 1080), (32, 32, 32)) + canvas.paste(portrait.resize((800, 450)), (x, 310)) + canvas.save(work / (name+".png")) + # A spatial pattern makes lingering crop observable; a uniform image cannot. + y, x = np.mgrid[:1080, :1920] + pattern = np.stack((x // 8 % 256, y // 5 % 256, (x // 16 + y // 16) % 256), axis=2).astype(np.uint8) + Image.fromarray(pattern).save(work / "no-face.png") + portrait.resize((1920, 1080)).save(work / "eyes.png") + proc.spawn() + await asyncio.to_thread(proc.wait_for, base.transport.process.READY_RE, 45) + await asyncio.to_thread(proc.wait_for_shutdown_control_ready, 10) + async with websockets.connect(f"ws://127.0.0.1:{proc.port}", max_size=24*1024*1024) as ws: + hello = json.loads(await ws.recv())["d"] + identify = {"rpcVersion": 1, "eventSubscriptions": 0} + if "authentication" in hello: + auth = hello["authentication"] + secret = base64.b64encode(hashlib.sha256((proc.password+auth["salt"]).encode()).digest()).decode() + identify["authentication"] = base64.b64encode(hashlib.sha256((secret+auth["challenge"]).encode()).digest()).decode() + await ws.send(json.dumps({"op": 1, "d": identify})) + assert json.loads(await ws.recv())["op"] == 2 + wire = base.transport.Wire(ws) + for item in (await wire.call("GetInputList"))["inputs"]: + if item["inputKind"].startswith("wasapi_"): + await wire.call("SetInputMute", {"inputName": item["inputName"], "inputMuted": True}) + caps = await wire.vendor("GetCapabilities", vendor="pulsar") + nv = caps["capabilities"]["nv_filters"] + report["nativeSdkCapability"] = nv + assert nv["same_frame_comparison"] is True + assert nv["ar"]["usable"] is True and nv["ar"]["version"] == "0.8.7.0", nv + inventory = {row["value"] for row in caps["capabilities"]["filters"]["values"]} + assert {"nv_autoframe_filter", "nv_eye_contact_filter"} <= inventory, inventory + report["gpuBefore"] = await wire.vendor("GetNvidiaGpuUsage", vendor="pulsar") + assert report["gpuBefore"]["available"] is True and report["gpuBefore"]["scope"] == "whole-device", report["gpuBefore"] + await wire.call("CreateScene", {"sceneName": "ArParity"}) + await wire.call("CreateInput", {"sceneName": "ArParity", "inputName": "Portrait", "inputKind": "image_source", "inputSettings": {"file": str(work / "left.png")}, "sceneItemEnabled": True}) + await wire.call("CreateScene", {"sceneName": "IdleParity"}) + await wire.call("SetCurrentProgramScene", {"sceneName": "IdleParity"}) + async def metrics(): + return await wire.vendor("GetNativeEffectMetrics", {"sourceName": "Portrait", "filterName": "idle"}, vendor="pulsar") + await wire.call("CreateSourceFilter", {"sourceName": "Portrait", "filterName": "idle", "filterKind": "nv_denoise_filter", "filterSettings": {"intensity": 1., "mode": 1}}) + await asyncio.sleep(2) + cold = await metrics() + assert cold["available"] and not cold["imagesAllocated"] and cold["processedFrames"] == 0, cold + await wire.call("SetCurrentProgramScene", {"sceneName": "ArParity"}) + await asyncio.sleep(3) + active = await metrics() + assert active["imagesAllocated"] and active["processedFrames"] >= 60 and not active["processingStopped"], active + await wire.call("SetCurrentProgramScene", {"sceneName": "IdleParity"}) + await asyncio.sleep(1) + idle_start = await metrics() + await asyncio.sleep(2) + idle_end = await metrics() + assert idle_end["processedFrames"] == idle_start["processedFrames"], (idle_start, idle_end) + await wire.call("SetCurrentProgramScene", {"sceneName": "ArParity"}) + await asyncio.sleep(2) + resumed = await metrics() + assert resumed["processedFrames"] > idle_end["processedFrames"] + 60, resumed + report["demandProcessing"] = {"cold": cold, "active": active, "idleStart": idle_start, "idleEnd": idle_end, "resumed": resumed} + await wire.call("RemoveSourceFilter", {"sourceName": "Portrait", "filterName": "idle"}) + async def image(source="Portrait"): + data = await wire.call("GetSourceScreenshot", {"sourceName": source, "imageFormat": "png", "imageWidth": 960, "imageHeight": 540}) + return np.asarray(Image.open(io.BytesIO(base64.b64decode(data["imageData"].split(",", 1)[1]))).convert("RGBA")) + recording_started = time.monotonic() + if record_video: + await wire.call("CreateInput", {"sceneName": "ArParity", "inputName": "Label", "inputKind": "text_gdiplus_v3", "inputSettings": {"text": "NVIDIA AR / qualification native\nFixture officielle NVIDIA / aucune camera", "font": {"face": "Segoe UI", "size": 36, "flags": 0}, "color": 0xffffff, "bk_color": 0x151515, "bk_opacity": 95}, "sceneItemEnabled": True}) + await wire.call("StartRecord") + recording_started = time.monotonic() + await asyncio.sleep(2) + async def caption(text): + if record_video: + await wire.call("SetInputSettings", {"inputName": "Label", "inputSettings": {"text": text}, "overlay": True}) + report.setdefault("timeline", []).append({"seconds": time.monotonic()-recording_started, "text": text}) + await wire.call("CreateSourceFilter", {"sourceName": "Portrait", "filterName": "ar", "filterKind": "nv_autoframe_filter", "filterSettings": {"intensity": 1., "zoom": 2.5, "smoothing": .15}}) + report["tracking"] = [] + for position in ["left", "right"]: + await wire.call("SetSourceFilterSettings", {"sourceName": "Portrait", "filterName": "ar", "filterSettings": {"intensity": 0.}, "overlay": True}) + await wire.call("SetInputSettings", {"inputName": "Portrait", "inputSettings": {"file": str(work / (position+".png"))}, "overlay": True}) + await asyncio.sleep(.3) + original = await image() + await wire.call("SetSourceFilterSettings", {"sourceName": "Portrait", "filterName": "ar", "filterSettings": {"intensity": 1.}, "overlay": True}) + await caption(f"NVIDIA Auto Frame / sujet a {position}\nSuivi natif du visage / zoom 2.5 maximum") + await asyncio.sleep(3) + processed = await image() + difference = float(np.abs(processed.astype(float)-original).mean()) + report["tracking"].append({"position": position, "difference": difference}) + assert difference > 2, report["tracking"][-1] + Image.fromarray(processed).save(work / (position+"-processed.png")) + await wire.call("SetSourceFilterSettings", {"sourceName": "Portrait", "filterName": "ar", "filterSettings": {"intensity": 0.}, "overlay": True}) + await wire.call("SetInputSettings", {"inputName": "Portrait", "inputSettings": {"file": str(work / "no-face.png")}, "overlay": True}) + await asyncio.sleep(.3) + no_face_original = await image() + await wire.call("SetSourceFilterSettings", {"sourceName": "Portrait", "filterName": "ar", "filterSettings": {"intensity": 1.}, "overlay": True}) + await caption("NVIDIA Auto Frame / aucun visage\nRetour progressif au cadrage d'origine") + await asyncio.sleep(.1) + initial_crop = float(np.abs((await image()).astype(float)-no_face_original).mean()) + await asyncio.sleep(4) + fallback_difference = float(np.abs((await image()).astype(float)-no_face_original).mean()) + report["noFaceFallback"] = {"initialCropDifference": initial_crop, "finalDifference": fallback_difference} + assert initial_crop > 2 and fallback_difference < .1, report["noFaceFallback"] + await wire.call("RemoveSourceFilter", {"sourceName": "Portrait", "filterName": "ar"}) + await wire.call("SetInputSettings", {"inputName": "Portrait", "inputSettings": {"file": str(work / "eyes.png")}, "overlay": True}) + await asyncio.sleep(.3) + original = await image() + await wire.call("CreateSourceFilter", {"sourceName": "Portrait", "filterName": "ar", "filterKind": "nv_eye_contact_filter", "filterSettings": {"intensity": 1., "compare": True}}) + await caption("NVIDIA Eye Contact / comparaison meme image\nGAUCHE : avant / DROITE : correction du regard") + await asyncio.sleep(3) + compared = await image() + left = float(np.abs(compared[:, :470].astype(float)-original[:, :470]).mean()) + right = float(np.abs(compared[:, 490:].astype(float)-original[:, 490:]).mean()) + report["eyeContact"] = {"leftDifference": left, "rightDifference": right} + assert left < .1 and right > .05, report["eyeContact"] + Image.fromarray(compared).save(work / "eye-contact-compared.png") + stats = await wire.call("GetStats") + await asyncio.sleep(5) + after = await wire.call("GetStats") + report["eyeContact"]["render"] = {"frames": after["renderTotalFrames"]-stats["renderTotalFrames"], "skipped": after["renderSkippedFrames"]-stats["renderSkippedFrames"], "meanMs": after["averageFrameRenderTime"]} + assert report["eyeContact"]["render"]["frames"] >= 270 and report["eyeContact"]["render"]["skipped"] == 0 + report["gpuWithEyeContact"] = await wire.vendor("GetNvidiaGpuUsage", vendor="pulsar") + await wire.call("SetSourceFilterSettings", {"sourceName": "Portrait", "filterName": "ar", "filterSettings": {"intensity": 0.}, "overlay": True}) + await caption("NVIDIA Eye Contact / intensite zero\nBypass exact de la correction") + await asyncio.sleep(1) + assert np.array_equal(original, await image()) + await wire.call("SetInputSettings", {"inputName": "Portrait", "inputSettings": {"file": str(work / "left.png")}, "overlay": True}) + await asyncio.sleep(.3) + stack_original = await image() + await wire.call("SetSourceFilterSettings", {"sourceName": "Portrait", "filterName": "ar", "filterSettings": {"intensity": .5, "compare": False}, "overlay": True}) + await wire.call("CreateSourceFilter", {"sourceName": "Portrait", "filterName": "frame", "filterKind": "nv_autoframe_filter", "filterSettings": {"intensity": 1., "zoom": 2., "smoothing": .15}}) + await caption("NVIDIA / effets AR simultanes\nAuto Frame + Eye Contact 50 %") + await asyncio.sleep(3) + stats = await wire.call("GetStats") + await asyncio.sleep(5) + after = await wire.call("GetStats") + report["stackedAr"] = {"frames": after["renderTotalFrames"]-stats["renderTotalFrames"], "skipped": after["renderSkippedFrames"]-stats["renderSkippedFrames"], "meanMs": after["averageFrameRenderTime"], "difference": float(np.abs((await image()).astype(float)-stack_original).mean())} + assert report["stackedAr"]["frames"] >= 270 and report["stackedAr"]["skipped"] == 0 and report["stackedAr"]["difference"] > 2, report["stackedAr"] + await wire.call("RemoveSourceFilter", {"sourceName": "Portrait", "filterName": "frame"}) + await wire.call("RemoveSourceFilter", {"sourceName": "Portrait", "filterName": "ar"}) + assert (await wire.call("GetSourceFilterList", {"sourceName": "Portrait"}))["filters"] == [] + if record_video: + await asyncio.sleep(2) + report["nativeRecording"] = (await wire.call("StopRecord"))["outputPath"] + report["checks"] = ["cold allocation and processing only on render demand", "idle processing stops and resumes", "actual AR registration", "face-follow and native zoom at two positions", "eye redirection same-frame comparison", "native render windows", "exact bypass", "cleanup and graceful shutdown"] + +base.exercise = exercise +if __name__ == "__main__": + sys.exit(base.main()) diff --git a/scripts/probe-nvidia-video-parity.py b/scripts/probe-nvidia-video-parity.py new file mode 100644 index 00000000..60e43b1c --- /dev/null +++ b/scripts/probe-nvidia-video-parity.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Native NVIDIA denoise and same-frame split regression. Synthetic pixels only. + +Reuses the installed-SDK probe's process, authenticated wire, diagnostics and +graceful shutdown. CLI and explicit --work/--report paths are identical. +""" +import asyncio +import base64 +import hashlib +import importlib.util +import io +import json +from pathlib import Path +import sys +import time + +import numpy as np +from PIL import Image +import websockets + +ROOT = Path(__file__).resolve().parents[1] +spec = importlib.util.spec_from_file_location("installed_nvidia_probe", ROOT / "scripts/probe-nvidia-effects.py") +base = importlib.util.module_from_spec(spec) +sys.modules[spec.name] = base +spec.loader.exec_module(base) + +async def exercise(proc, work, report, record_video=False): + proc.spawn() + await asyncio.to_thread(proc.wait_for, base.transport.process.READY_RE, 45) + await asyncio.to_thread(proc.wait_for_shutdown_control_ready, 10) + async with websockets.connect(f"ws://127.0.0.1:{proc.port}", max_size=24*1024*1024) as ws: + hello = json.loads(await ws.recv())["d"] + identify = {"rpcVersion": 1, "eventSubscriptions": 0} + if "authentication" in hello: + auth = hello["authentication"] + secret = base64.b64encode(hashlib.sha256((proc.password + auth["salt"]).encode()).digest()).decode() + identify["authentication"] = base64.b64encode(hashlib.sha256((secret + auth["challenge"]).encode()).digest()).decode() + await ws.send(json.dumps({"op": 1, "d": identify})) + assert json.loads(await ws.recv())["op"] == 2 + wire = base.transport.Wire(ws) + for item in (await wire.call("GetInputList"))["inputs"]: + if item["inputKind"].startswith("wasapi_"): + await wire.call("SetInputMute", {"inputName": item["inputName"], "inputMuted": True}) + caps = await wire.vendor("GetCapabilities", vendor="pulsar") + report["capabilities"] = caps + inventory = {row["value"] for row in caps["capabilities"]["filters"]["values"]} + assert "nv_denoise_filter" in inventory + await wire.call("CreateScene", {"sceneName": "VideoParity"}) + rng = np.random.default_rng(20261010) + half = (rng.normal(96, 30, (1080, 960, 3))).clip(0, 255).astype(np.uint8) + half[:, 300:315] = 230 # preserve a high-contrast feature + pixels = np.concatenate([half, half], axis=1) + Image.fromarray(pixels).save(work / "noisy.png") + await wire.call("CreateInput", {"sceneName": "VideoParity", "inputName": "Noisy", "inputKind": "image_source", "inputSettings": {"file": str(work / "noisy.png")}, "sceneItemEnabled": True}) + await wire.call("SetCurrentProgramScene", {"sceneName": "VideoParity"}) + async def image(source="Noisy"): + data = await wire.call("GetSourceScreenshot", {"sourceName": source, "imageFormat": "png", "imageWidth": 960, "imageHeight": 540}) + return np.asarray(Image.open(io.BytesIO(base64.b64decode(data["imageData"].split(",", 1)[1]))).convert("RGBA")) + await asyncio.sleep(1) + original = await image() + recording_started = time.monotonic() + if record_video: + await wire.call("CreateInput", {"sceneName": "VideoParity", "inputName": "Label", "inputKind": "text_gdiplus_v3", "inputSettings": {"text": "NVIDIA / Video Noise Removal\nOriginal - fixture synthetique 1080p60", "font": {"face": "Segoe UI", "size": 36, "flags": 0}, "color": 0xffffff, "bk_color": 0x151515, "bk_opacity": 95}, "sceneItemEnabled": True}) + await wire.call("StartRecord") + recording_started = time.monotonic() + await asyncio.sleep(2) + async def caption(text): + if record_video: + await wire.call("SetInputSettings", {"inputName": "Label", "inputSettings": {"text": text}, "overlay": True}) + report.setdefault("timeline", []).append({"seconds": time.monotonic()-recording_started, "text": text}) + report["denoise"] = [] + await wire.call("CreateSourceFilter", {"sourceName": "Noisy", "filterName": "parity", "filterKind": "nv_denoise_filter", "filterSettings": {"intensity": 1., "mode": 0}}) + for mode in [0, 1]: + await caption(f"NVIDIA / Video Noise Removal\nMode {mode} - intensite 100 %") + await wire.call("SetSourceFilterSettings", {"sourceName": "Noisy", "filterName": "parity", "filterSettings": {"mode": mode}, "overlay": True}) + await asyncio.sleep(3) + processed = await image() + region = (slice(100, 450), slice(10, 120), slice(0, 3)) + deviation = float(np.abs(processed.astype(float)-original).mean()) + entry = {"mode": mode, "meanDifference": deviation, "originalNoiseStd": float(original[region].std()), "processedNoiseStd": float(processed[region].std()), "alphaPreserved": bool(np.array_equal(original[:, :, 3], processed[:, :, 3]))} + report["denoise"].append(entry) + # A static noisy image is treated as persistent texture by the + # temporal model. Require an observed reduction, not an arbitrary + # 15% quality target that contradicts the detail-preserving mode. + assert deviation > 1 and entry["processedNoiseStd"] < entry["originalNoiseStd"]*.99, entry + assert entry["alphaPreserved"] + await wire.call("SetSourceFilterSettings", {"sourceName": "Noisy", "filterName": "parity", "filterSettings": {"compare": True}, "overlay": True}) + await caption(f"NVIDIA / comparaison meme image\nGAUCHE : avant / DROITE : traitement mode {mode}") + await asyncio.sleep(1) + comparison = await image() + left_difference = float(np.abs(comparison[:, :470].astype(float)-original[:, :470]).mean()) + right_difference = float(np.abs(comparison[:, 490:].astype(float)-original[:, 490:]).mean()) + entry["comparison"] = {"leftDifference": left_difference, "rightDifference": right_difference} + assert left_difference < .1 and right_difference > 1, entry + program = await image("VideoParity") + assert np.abs(program[80:, 490:].astype(float)-original[80:, 490:]).mean() > 1 + stats = await wire.call("GetStats") + await asyncio.sleep(3) + after = await wire.call("GetStats") + entry["render"] = {"frames": after["renderTotalFrames"]-stats["renderTotalFrames"], "skipped": after["renderSkippedFrames"]-stats["renderSkippedFrames"], "meanMs": after["averageFrameRenderTime"]} + assert entry["render"]["frames"] >= 160 and entry["render"]["skipped"] == 0, entry + await wire.call("SetSourceFilterSettings", {"sourceName": "Noisy", "filterName": "parity", "filterSettings": {"compare": False}, "overlay": True}) + await wire.call("SetSourceFilterSettings", {"sourceName": "Noisy", "filterName": "parity", "filterSettings": {"intensity": 0.}, "overlay": True}) + await caption("NVIDIA / Video Noise Removal\nIntensite 0 % : bypass exact") + await asyncio.sleep(1) + assert np.array_equal(original, await image()) + for intensity in [.25, .75]: + await wire.call("SetSourceFilterSettings", {"sourceName": "Noisy", "filterName": "parity", "filterSettings": {"intensity": intensity}, "overlay": True}) + await asyncio.sleep(1) + report.setdefault("liveIntensity", []).append({"intensity": intensity, "difference": float(np.abs((await image()).astype(float)-original).mean())}) + assert report["liveIntensity"][1]["difference"] > report["liveIntensity"][0]["difference"]*2 + await wire.call("RemoveSourceFilter", {"sourceName": "Noisy", "filterName": "parity"}) + report["comparisonKinds"] = [] + for kind in ["nv_blur_filter", "nv_background_blur_filter", "nv_greenscreen_filter"]: + settings = {"intensity": .8, "compare": True} if kind != "nv_greenscreen_filter" else {"mode": 1, "threshold": .8, "processing_interval": 1, "compare": True} + await wire.call("CreateSourceFilter", {"sourceName": "Noisy", "filterName": "parity", "filterKind": kind, "filterSettings": settings}) + await asyncio.sleep(2) + compared = await image() + left = float(np.abs(compared[:, :470].astype(float)-original[:, :470]).mean()) + right = float(np.abs(compared[:, 490:].astype(float)-original[:, 490:]).mean()) + report["comparisonKinds"].append({"kind": kind, "leftDifference": left, "rightDifference": right}) + assert left < .1 and right > 1, report["comparisonKinds"][-1] + await wire.call("RemoveSourceFilter", {"sourceName": "Noisy", "filterName": "parity"}) + assert (await wire.call("GetSourceFilterList", {"sourceName": "Noisy"}))["filters"] == [] + if record_video: + await caption("NVIDIA / fin du test\n2 modes / intensite / bypass / comparaison des 4 VFX") + await asyncio.sleep(2) + report["nativeRecording"] = (await wire.call("StopRecord"))["outputPath"] + report["checks"] = ["actual SDK denoising and temporal state", "two denoise models reduce synthetic noise", "same-frame left/right comparison for four VFX", "live blend intensity and exact bypass", "native Program pixels", "1080p60 render windows", "cleanup and graceful shutdown"] + +base.exercise = exercise +if __name__ == "__main__": + sys.exit(base.main()) diff --git a/tests/nv-probe/nv-probe-test.c b/tests/nv-probe/nv-probe-test.c index 99e24e37..3672b48e 100644 --- a/tests/nv-probe/nv-probe-test.c +++ b/tests/nv-probe/nv-probe-test.c @@ -475,11 +475,16 @@ static void test_fallback_root_is_not_environment(void) char progfiles[MAX_PATH]; char poisoned[MAX_PATH]; struct pulsar_nv_sdk_probe p; + struct pulsar_nv_sdk_probe baseline; section("VFX fallback root -- SHGetKnownFolderPath, never %ProgramFiles%"); check(pulsar_nv_program_files(progfiles, sizeof(progfiles)) && progfiles[0] != '\0', "the known-folder API answers for FOLDERID_ProgramFiles"); + /* The system may already contain the real SDK. Compare its observed + * baseline instead of assuming every development machine has no SDK. */ + SetEnvironmentVariableA(PULSAR_NV_VFX_DIR_ENV, NULL); + pulsar_nv_probe_vfx(&baseline); /* Point %ProgramFiles% at a directory we control and fully stock, * exactly as a hostile parent process would. If the fallback still @@ -499,7 +504,10 @@ static void test_fallback_root_is_not_environment(void) SetEnvironmentVariableA("ProgramFiles", poisoned); SetEnvironmentVariableA(PULSAR_NV_VFX_DIR_ENV, NULL); pulsar_nv_probe_vfx(&p); - check(!p.dir_valid && !p.usable, + check(p.dir_valid == baseline.dir_valid && p.usable == baseline.usable && + p.dlls_present == baseline.dlls_present && p.models_present == baseline.models_present && + p.version == baseline.version && p.version_ok == baseline.version_ok && + strcmp(p.dir, baseline.dir) == 0, "poisoning %ProgramFiles% with a complete, non-writable fake SDK changes nothing"); { From cbf6f7773bd383808cb95ccd7ecff262f3fa391c Mon Sep 17 00:00:00 2001 From: ClodoCapeo <159788250+ClodoCapeo@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:24:25 +0200 Subject: [PATCH 17/17] test(nvidia): qualify image and looping video backgrounds Preserve the segmented subject in native Program while replacing the image and looping video beneath it. Publish current candidate video hashes and qualification limits. Agent-Role: eleven Agent-Thread: 01a11b19-56f8-7e33-a1c7-9ddfe1bb96c7 Work-Unit: local-20261009-capability-audit Issue: local-20261009-capability-audit --- .../20261010T140600Z-AGENT_CHECKPOINT.json | 2 +- .../20261010T140732Z-nvidia-ar-combined.json | 199 ++++++++++++++++ .../20261010T140732Z-video-manifest.json | 87 +++++++ ...010T142207Z-nvidia-camera-video-proof.json | 224 ++++++++++++++++++ .../20261010T142207Z-video-manifest.json | 92 +++++++ .../eleven/20261010T142407Z-AGENT_REPORT.json | 42 ++++ plugins/pulsar-nv-secure-load/README.md | 7 +- scripts/probe-nvidia-ar-parity.py | 44 +++- 8 files changed, 693 insertions(+), 4 deletions(-) create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T140732Z-nvidia-ar-combined.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T140732Z-video-manifest.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T142207Z-nvidia-camera-video-proof.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T142207Z-video-manifest.json create mode 100644 evidence/local-20261009-capability-audit/eleven/20261010T142407Z-AGENT_REPORT.json diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T140600Z-AGENT_CHECKPOINT.json b/evidence/local-20261009-capability-audit/eleven/20261010T140600Z-AGENT_CHECKPOINT.json index 477f9d31..a567b1cc 100644 --- a/evidence/local-20261009-capability-audit/eleven/20261010T140600Z-AGENT_CHECKPOINT.json +++ b/evidence/local-20261009-capability-audit/eleven/20261010T140600Z-AGENT_CHECKPOINT.json @@ -2,7 +2,7 @@ "worktree": "D:/Documents/Zab/Pulsar/.worktrees/eleven-local-20261009-capability-audit", "branch": "eleven/local-20261009-capability-audit", "base": "e51467937d65f935d1a598237287815bef04677f", - "nativeSource": "1246de1a4f0df3f2", + "nativeSource": "1246de1a4f0df3f12034dd6fdbdea75e4a8bddc1", "nvFiltersSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", "publisherSha256": "e231c260fcb5d145b50aac9f8f24a022d3e4ada7bde2af92345e9e23bfa02991", "validation": { diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T140732Z-nvidia-ar-combined.json b/evidence/local-20261009-capability-audit/eleven/20261010T140732Z-nvidia-ar-combined.json new file mode 100644 index 00000000..1ba9bf05 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T140732Z-nvidia-ar-combined.json @@ -0,0 +1,199 @@ +{ + "started": 1791641091.2904906, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "nvFiltersSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "fixture": { + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-ar-fixtures\\ar_001.png", + "sha256": "a196d219b924dcbb1b6c3849b781f15d61328a9066e3f49c71f689cb58594b8b" + }, + "nativeSdkCapability": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "ar": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.8.7.0", + "models_present": true, + "usable": true, + "version": "0.8.7.0" + }, + "module_loaded": true, + "same_frame_comparison": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "gpuBefore": { + "available": true, + "devices": [ + { + "gpuPercent": 12, + "index": 0, + "memoryPercent": 2, + "name": "NVIDIA GeForce RTX 3070", + "totalMemoryBytes": 8589934592, + "usedMemoryBytes": 4489633792, + "uuid": "GPU-a3fc3a1a-36da-1c8e-e8fb-e73e4ab35bbe" + } + ], + "reason": "available", + "scope": "whole-device" + }, + "demandProcessing": { + "cold": { + "available": true, + "imagesAllocated": false, + "lastProcessingMs": 0.0, + "processedFrames": 0, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "active": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 5.4339, + "processedFrames": 177, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "idleStart": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 5.4339, + "processedFrames": 177, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "idleEnd": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 5.4339, + "processedFrames": 177, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "resumed": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 5.7653, + "processedFrames": 298, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + } + }, + "tracking": [ + { + "position": "left", + "difference": 20.560814043209877 + }, + { + "position": "right", + "difference": 19.514062982253087 + } + ], + "timeline": [ + { + "seconds": 2.422000000005937, + "text": "NVIDIA Auto Frame / sujet a left\nSuivi natif du visage / zoom 2.5 maximum" + }, + { + "seconds": 5.968000000008033, + "text": "NVIDIA Auto Frame / sujet a right\nSuivi natif du visage / zoom 2.5 maximum" + }, + { + "seconds": 9.468000000008033, + "text": "NVIDIA Auto Frame / aucun visage\nRetour progressif au cadrage d'origine" + }, + { + "seconds": 14.453000000008615, + "text": "NVIDIA Eye Contact / comparaison meme image\nGAUCHE : avant / DROITE : correction du regard" + }, + { + "seconds": 22.953000000008615, + "text": "NVIDIA Eye Contact / intensite zero\nBypass exact de la correction" + }, + { + "seconds": 24.734000000011292, + "text": "NVIDIA / effets AR simultanes\nAuto Frame + Eye Contact 50 %" + } + ], + "noFaceFallback": { + "initialCropDifference": 16.10308159722222, + "finalDifference": 0.0125 + }, + "eyeContact": { + "leftDifference": 0.0, + "rightDifference": 0.2691016548463357, + "render": { + "frames": 301, + "skipped": 0, + "meanMs": 8.725745 + } + }, + "gpuWithEyeContact": { + "available": true, + "devices": [ + { + "gpuPercent": 34, + "index": 0, + "memoryPercent": 7, + "name": "NVIDIA GeForce RTX 3070", + "totalMemoryBytes": 8589934592, + "usedMemoryBytes": 5251129344, + "uuid": "GPU-a3fc3a1a-36da-1c8e-e8fb-e73e4ab35bbe" + } + ], + "reason": "available", + "scope": "whole-device" + }, + "stackedAr": { + "frames": 300, + "skipped": 0, + "meanMs": 10.106944, + "difference": 20.597167245370372 + }, + "nativeRecording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-ar-combined-qualified\\pulsar-20261010-160503.mp4", + "checks": [ + "cold allocation and processing only on render demand", + "idle processing stops and resumes", + "actual AR registration", + "face-follow and native zoom at two positions", + "eye redirection same-frame comparison", + "native render windows", + "exact bypass", + "cleanup and graceful shutdown" + ], + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T14:04:52Z INFO ui25E5VHLxr6BCXF libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T14:04:52Z INFO ui25E5VHLxr6BCXF libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T14:04:52Z INFO ui25E5VHLxr6BCXF NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T14:04:52Z INFO ui25E5VHLxr6BCXF NVIDIA Audio Effects: | enabled", + "2026-10-10T14:04:52Z INFO ui25E5VHLxr6BCXF NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T14:04:52Z INFO ui25E5VHLxr6BCXF NVIDIA VIDEO FX | : enabled, redistributable found" + ], + "sdkDiagnostics": [], + "qualification": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity", + "passed": true +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T140732Z-video-manifest.json b/evidence/local-20261009-capability-audit/eleven/20261010T140732Z-video-manifest.json new file mode 100644 index 00000000..3aa35bfa --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T140732Z-video-manifest.json @@ -0,0 +1,87 @@ +{ + "nativeSource": "1246de1a4f0df3f12034dd6fdbdea75e4a8bddc1", + "pulsarCommit": "a7da349e3c00c7ceb9f85278fd9f6ecd5fe32d8f", + "videos": { + "Pulsar-AutoFrame-EyeContact.mp4": { + "sha256": "45ad586256e9af3f705eedf669a0d30ed67aa975a051b34871fcffaa2eb92bac", + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1920, + "height": 1080, + "r_frame_rate": "60/1" + }, + { + "codec_name": "aac", + "r_frame_rate": "0/0" + } + ], + "format": { + "duration": "34.933333", + "size": "26263073" + } + }, + "fixture": { + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-ar-fixtures\\ar_001.png", + "sha256": "a196d219b924dcbb1b6c3849b781f15d61328a9066e3f49c71f689cb58594b8b" + }, + "nativeDllSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "scope": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity" + }, + "Pulsar-VideoNoiseRemoval.mp4": { + "sha256": "f746069bf6497390f92efd7aa574567933ea9f76ad1b34b13be05f23bb4b4d72", + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1920, + "height": 1080, + "r_frame_rate": "60/1" + }, + { + "codec_name": "aac", + "r_frame_rate": "0/0" + } + ], + "format": { + "duration": "29.418667", + "size": "22315394" + } + }, + "fixture": "Synthetic stripes/noise", + "nativeDllSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "scope": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity" + }, + "Pulsar-Audio-Video-Regression.mp4": { + "sha256": "418c375674b53f2868509344adcb6e28a18ab4ad92920b6fada74c847e7bdeba", + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1920, + "height": 1080, + "r_frame_rate": "60/1" + }, + { + "codec_name": "aac", + "r_frame_rate": "0/0" + } + ], + "format": { + "duration": "123.370667", + "size": "93857405" + } + }, + "fixture": "Synthetic stripes/noise", + "nativeDllSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "scope": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity" + } + } +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T142207Z-nvidia-camera-video-proof.json b/evidence/local-20261009-capability-audit/eleven/20261010T142207Z-nvidia-camera-video-proof.json new file mode 100644 index 00000000..0fd991fe --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T142207Z-nvidia-camera-video-proof.json @@ -0,0 +1,224 @@ +{ + "started": 1791641479.2131546, + "mode": "isolated installed NVIDIA SDK synthetic regression", + "exe": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\audit-runtime\\bin\\64bit\\pulsar.exe", + "exeSha256": "fac3466e90a744bff14ca68dac2967e5a51ecd610e9fcf1c1381f8b203299ac8", + "nvFiltersSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "sdkPaths": { + "afx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Audio Effects SDK", + "vfx": "C:\\Program Files\\NVIDIA Corporation\\NVIDIA Video Effects" + }, + "fixture": { + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-ar-fixtures\\ar_001.png", + "sha256": "a196d219b924dcbb1b6c3849b781f15d61328a9066e3f49c71f689cb58594b8b" + }, + "nativeSdkCapability": { + "afx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "1.6.1.2", + "models_present": true, + "usable": true, + "version": "1.6.1.2" + }, + "applicability": "read-only", + "ar": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.8.7.0", + "models_present": true, + "usable": true, + "version": "0.8.7.0" + }, + "module_loaded": true, + "same_frame_comparison": true, + "vfx": { + "directory_designated": true, + "dlls_present": true, + "min_version": "0.7.6.0", + "models_present": true, + "usable": true, + "version": "0.7.6.0" + } + }, + "gpuBefore": { + "available": true, + "devices": [ + { + "gpuPercent": 14, + "index": 0, + "memoryPercent": 2, + "name": "NVIDIA GeForce RTX 3070", + "totalMemoryBytes": 8589934592, + "usedMemoryBytes": 4328009728, + "uuid": "GPU-a3fc3a1a-36da-1c8e-e8fb-e73e4ab35bbe" + } + ], + "reason": "available", + "scope": "whole-device" + }, + "demandProcessing": { + "cold": { + "available": true, + "imagesAllocated": false, + "lastProcessingMs": 0.0, + "processedFrames": 0, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "active": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 4.7636, + "processedFrames": 171, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "idleStart": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 4.7636, + "processedFrames": 171, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "idleEnd": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 4.7636, + "processedFrames": 171, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + }, + "resumed": { + "available": true, + "imagesAllocated": true, + "lastProcessingMs": 5.3729, + "processedFrames": 291, + "processingStopped": false, + "scope": "native-pipeline-wall-time" + } + }, + "tracking": [ + { + "position": "left", + "difference": 20.560814043209877 + }, + { + "position": "right", + "difference": 19.514062982253087 + } + ], + "timeline": [ + { + "seconds": 2.4690000000118744, + "text": "NVIDIA Auto Frame / sujet a left\nSuivi natif du visage / zoom 2.5 maximum" + }, + { + "seconds": 6.125, + "text": "NVIDIA Auto Frame / sujet a right\nSuivi natif du visage / zoom 2.5 maximum" + }, + { + "seconds": 9.65700000000652, + "text": "NVIDIA Auto Frame / aucun visage\nRetour progressif au cadrage d'origine" + }, + { + "seconds": 14.75, + "text": "NVIDIA Eye Contact / comparaison meme image\nGAUCHE : avant / DROITE : correction du regard" + }, + { + "seconds": 23.297000000005937, + "text": "NVIDIA Eye Contact / intensite zero\nBypass exact de la correction" + }, + { + "seconds": 25.03200000000652, + "text": "NVIDIA / effets AR simultanes\nAuto Frame + Eye Contact 50 %" + }, + { + "seconds": 33.26600000000326, + "text": "NVIDIA / remplacement d'arriere-plan\nDetourage natif / fond image sous la personne" + }, + { + "seconds": 38.86000000000058, + "text": "NVIDIA / remplacement d'arriere-plan\nFond VIDEO en boucle / personne detouree native" + } + ], + "noFaceFallback": { + "initialCropDifference": 16.10308159722222, + "finalDifference": 0.0125 + }, + "eyeContact": { + "leftDifference": 0.0, + "rightDifference": 0.2691085500394011, + "render": { + "frames": 300, + "skipped": 0, + "meanMs": 7.761301 + } + }, + "gpuWithEyeContact": { + "available": true, + "devices": [ + { + "gpuPercent": 37, + "index": 0, + "memoryPercent": 7, + "name": "NVIDIA GeForce RTX 3070", + "totalMemoryBytes": 8589934592, + "usedMemoryBytes": 4826578944, + "uuid": "GPU-a3fc3a1a-36da-1c8e-e8fb-e73e4ab35bbe" + } + ], + "reason": "available", + "scope": "whole-device" + }, + "stackedAr": { + "frames": 301, + "skipped": 0, + "meanMs": 9.706826, + "difference": 20.597655285493826 + }, + "backgroundComposition": { + "opaquePixels": 156794, + "transparentPixels": 284526, + "subjectDifference": 0.0010746584690740717, + "backgroundDifference": 70.99850189437872 + }, + "videoBackground": { + "media": { + "mediaCursor": 2600, + "mediaDuration": 3000, + "mediaState": "OBS_MEDIA_STATE_PLAYING" + }, + "backgroundDifference": 3.779048311929314, + "subjectDifference": 0.0014700817633327805 + }, + "nativeRecording": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-camera-video-qualified\\pulsar-20261010-161133.mp4", + "checks": [ + "cold allocation and processing only on render demand", + "idle processing stops and resumes", + "actual AR registration", + "face-follow and native zoom at two positions", + "eye redirection same-frame comparison", + "stacked AR effects", + "segmented portrait over authored background in native Program", + "native render windows", + "exact bypass", + "cleanup and graceful shutdown" + ], + "shutdown": { + "exitCode": 0, + "forced": false + }, + "sdkLogs": [ + "2026-10-10T14:11:21Z INFO 7HcBFTkM9im8ODuZ libobs | \tAdapter 0: NVIDIA GeForce RTX 3070", + "2026-10-10T14:11:21Z INFO 7HcBFTkM9im8ODuZ libobs | Loading up D3D11 on adapter NVIDIA GeForce RTX 3070 (0)", + "2026-10-10T14:11:22Z INFO 7HcBFTkM9im8ODuZ NVIDIA Audio Effects: | version: 1.6.1.2", + "2026-10-10T14:11:22Z INFO 7HcBFTkM9im8ODuZ NVIDIA Audio Effects: | enabled", + "2026-10-10T14:11:22Z INFO 7HcBFTkM9im8ODuZ NVIDIA VIDEO FX | : NVIDIA VIDEO FX version: 0.7.6.0", + "2026-10-10T14:11:22Z INFO 7HcBFTkM9im8ODuZ NVIDIA VIDEO FX | : enabled, redistributable found" + ], + "sdkDiagnostics": [], + "qualification": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity", + "passed": true +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T142207Z-video-manifest.json b/evidence/local-20261009-capability-audit/eleven/20261010T142207Z-video-manifest.json new file mode 100644 index 00000000..50aa8205 --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T142207Z-video-manifest.json @@ -0,0 +1,92 @@ +{ + "nativeSource": "1246de1a4f0df3f12034dd6fdbdea75e4a8bddc1", + "pulsarCommit": "a7da349e3c00c7ceb9f85278fd9f6ecd5fe32d8f", + "refusedByProductDecision": [ + "Studio Voice", + "Virtual Key Light" + ], + "createdUtc": "20261010T142207Z", + "videos": { + "Pulsar-AutoFrame-EyeContact.mp4": { + "sha256": "e01425c58aec26853600ffe23fe29cbb8050d85d892052ffa4a29fdd9bea791c", + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1920, + "height": 1080, + "r_frame_rate": "60/1" + }, + { + "codec_name": "aac", + "r_frame_rate": "0/0" + } + ], + "format": { + "duration": "46.533333", + "size": "35010385" + } + }, + "scope": "Native Program: AR, exact bypass, stacked AR, segmentation, image and looping video background; official static portrait fixture.", + "fixture": { + "path": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit\\.cache\\nvidia-ar-fixtures\\ar_001.png", + "sha256": "a196d219b924dcbb1b6c3849b781f15d61328a9066e3f49c71f689cb58594b8b" + }, + "nativeDllSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872" + }, + "Pulsar-VideoNoiseRemoval.mp4": { + "sha256": "f746069bf6497390f92efd7aa574567933ea9f76ad1b34b13be05f23bb4b4d72", + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1920, + "height": 1080, + "r_frame_rate": "60/1" + }, + { + "codec_name": "aac", + "r_frame_rate": "0/0" + } + ], + "format": { + "duration": "29.418667", + "size": "22315394" + } + }, + "fixture": "Synthetic stripes/noise", + "nativeDllSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "scope": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity" + }, + "Pulsar-Audio-Video-Regression.mp4": { + "sha256": "418c375674b53f2868509344adcb6e28a18ab4ad92920b6fada74c847e7bdeba", + "probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1920, + "height": 1080, + "r_frame_rate": "60/1" + }, + { + "codec_name": "aac", + "r_frame_rate": "0/0" + } + ], + "format": { + "duration": "123.370667", + "size": "93857405" + } + }, + "fixture": "Synthetic stripes/noise", + "nativeDllSha256": "023816f1ae8916ecb2c32528b098d85c6585318fe2887ece4ab5cf45279b6872", + "scope": "installed-SDK synthetic regression; excludes voice/person quality, full-package CI and hardware capacity" + } + } +} diff --git a/evidence/local-20261009-capability-audit/eleven/20261010T142407Z-AGENT_REPORT.json b/evidence/local-20261009-capability-audit/eleven/20261010T142407Z-AGENT_REPORT.json new file mode 100644 index 00000000..a21c334e --- /dev/null +++ b/evidence/local-20261009-capability-audit/eleven/20261010T142407Z-AGENT_REPORT.json @@ -0,0 +1,42 @@ +{ + "worktree": "D:\\Documents\\Zab\\Pulsar\\.worktrees\\eleven-local-20261009-capability-audit", + "branch": "eleven/local-20261009-capability-audit", + "base": "e51467937d65f935d1a598237287815bef04677f", + "sourceHeadBeforeEvidenceCommit": "a7da349e3c00c7ceb9f85278fd9f6ecd5fe32d8f", + "scope": "NVIDIA capability qualification and explicit product refusals", + "modifiedBeforeEvidenceCommit": [ + " M evidence/local-20261009-capability-audit/eleven/20261010T140600Z-AGENT_CHECKPOINT.json", + " M plugins/pulsar-nv-secure-load/README.md", + " M scripts/probe-nvidia-ar-parity.py", + "?? .cache/", + "?? evidence/local-20261009-capability-audit/eleven/20261010T140732Z-nvidia-ar-combined.json", + "?? evidence/local-20261009-capability-audit/eleven/20261010T140732Z-video-manifest.json", + "?? evidence/local-20261009-capability-audit/eleven/20261010T142207Z-nvidia-camera-video-proof.json", + "?? evidence/local-20261009-capability-audit/eleven/20261010T142207Z-video-manifest.json" + ], + "artifacts": "D:/Documents/Zab/Artifacts/2026-10-10/nvidia-parity", + "fullBroadcastParityClaimed": false, + "productRefused": [ + "Studio Voice", + "Virtual Key Light" + ], + "remaining": [ + "Windows virtual audio endpoints require a driver", + "Effective multi-GPU selection absent", + "Stream Deck uses keyboard bindings only; no plugin or hardware proof", + "Physical webcam/voice quality, prolonged sessions and full release CI not exhaustively qualified" + ], + "retainedWorktreeReason": "Draft pull request and active shared dev qualification; unmerged work must be preserved", + "temporaryCacheReason": "Owned build/SDK fixture and proof runtime cache retained for reproducibility; not staged", + "validation": { + "nativeCamera": "Cold/idle/resume, AR follow/gaze/bypass, stacked AR, segmentation and image/looping-video Program composition passed", + "nativeDenoise": "Both models and same-frame comparison passed", + "nativeAudioVFX": "Three AFX methods, old VFX, 20 switches, rapid update/removal and graceful exit passed", + "transientFailure": "First broad run skipped one blur frame; failure retained alongside passing unchanged rerun", + "loader": "49 checks passed", + "build": "Focused nv-filters and publisher builds passed", + "patchReplay": "0063/0064 match normalized native source at 1246de1a4f0df3f12034dd6fdbdea75e4a8bddc1", + "packaging": "No SDK binary/model payload source gate passed", + "pythonCompile": "AR probe passed" + } +} diff --git a/plugins/pulsar-nv-secure-load/README.md b/plugins/pulsar-nv-secure-load/README.md index 37ac1587..530c2b83 100644 --- a/plugins/pulsar-nv-secure-load/README.md +++ b/plugins/pulsar-nv-secure-load/README.md @@ -90,8 +90,11 @@ binary or model is distributed. This is not per-effect GPU attribution. `probe-nvidia-video-parity.py` checks both denoise models, alpha preservation, live blend and four same-frame VFX splits. `probe-nvidia-ar-parity.py` takes `--portrait`, checks face translation, no-face fallback, gaze correction, -cold allocation, idle inference counts and resumption. No physical camera or -microphone is selected by these tests. +cold allocation, idle inference counts and resumption. It also checks stacked +AR effects and native Program composition of the segmented portrait over an +image and a looping video, preserving opaque subject pixels. FFmpeg must be +on PATH to generate the three-second video fixture inside `--work`. +No physical camera or microphone is selected by these tests. [`scripts/probe-nvidia-effects.py`](../../scripts/probe-nvidia-effects.py) is an explicit hardware test for a machine with the official compatible AFX/VFX diff --git a/scripts/probe-nvidia-ar-parity.py b/scripts/probe-nvidia-ar-parity.py index 8bcbd6e3..530da413 100644 --- a/scripts/probe-nvidia-ar-parity.py +++ b/scripts/probe-nvidia-ar-parity.py @@ -13,6 +13,7 @@ import json from pathlib import Path import sys +import subprocess import time import numpy as np @@ -41,6 +42,13 @@ async def exercise(proc, work, report, record_video=False): pattern = np.stack((x // 8 % 256, y // 5 % 256, (x // 16 + y // 16) % 256), axis=2).astype(np.uint8) Image.fromarray(pattern).save(work / "no-face.png") portrait.resize((1920, 1080)).save(work / "eyes.png") + subprocess.run( + ["ffmpeg", "-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", + "testsrc2=size=1920x1080:rate=30", "-t", "3", "-an", "-c:v", + "libx264", "-preset", "ultrafast", "-crf", "24", "-y", + str(work / "background-video.mp4")], + check=True, + ) proc.spawn() await asyncio.to_thread(proc.wait_for, base.transport.process.READY_RE, 45) await asyncio.to_thread(proc.wait_for_shutdown_control_ready, 10) @@ -170,10 +178,44 @@ async def caption(text): await wire.call("RemoveSourceFilter", {"sourceName": "Portrait", "filterName": "frame"}) await wire.call("RemoveSourceFilter", {"sourceName": "Portrait", "filterName": "ar"}) assert (await wire.call("GetSourceFilterList", {"sourceName": "Portrait"}))["filters"] == [] + await wire.call("SetInputSettings", {"inputName": "Portrait", "inputSettings": {"file": str(work / "eyes.png")}, "overlay": True}) + await wire.call("CreateSourceFilter", {"sourceName": "Portrait", "filterName": "segment", "filterKind": "nv_greenscreen_filter", "filterSettings": {"mode": 0, "threshold": .5, "processing_interval": 1}}) + await caption("NVIDIA / remplacement d'arriere-plan\nDetourage natif / fond image sous la personne") + Image.new("RGB", (1920, 1080), (48, 110, 190)).save(work / "background-blue.png") + Image.new("RGB", (1920, 1080), (190, 110, 48)).save(work / "background-red.png") + created = await wire.call("CreateInput", {"sceneName": "ArParity", "inputName": "Background", "inputKind": "image_source", "inputSettings": {"file": str(work / "background-blue.png")}, "sceneItemEnabled": True}) + await wire.call("SetSceneItemIndex", {"sceneName": "ArParity", "sceneItemId": created["sceneItemId"], "sceneItemIndex": 0}) + await asyncio.sleep(3) + segmented = await image() + assert segmented[:,:,3].min() == 0 and segmented[:,:,3].max() == 255, "Portrait must contain both transparent background and opaque subject" + blue = await image("ArParity") + await wire.call("SetInputSettings", {"inputName": "Background", "inputSettings": {"file": str(work / "background-red.png")}, "overlay": True}) + await asyncio.sleep(2) + red = await image("ArParity") + opaque = segmented[:,:,3] == 255 + transparent = segmented[:,:,3] == 0 + # Ignore the caption area when comparing the composited Program pixels. + opaque[:80] = False; transparent[:80] = False + report["backgroundComposition"] = {"opaquePixels": int(opaque.sum()), "transparentPixels": int(transparent.sum()), "subjectDifference": float(np.abs(blue.astype(float)-red)[opaque].mean()), "backgroundDifference": float(np.abs(blue.astype(float)-red)[transparent].mean())} + assert report["backgroundComposition"]["subjectDifference"] < .1 and report["backgroundComposition"]["backgroundDifference"] > 10, report["backgroundComposition"] + Image.fromarray(red).save(work / "background-replacement.png") + await wire.call("RemoveInput", {"inputName": "Background"}) + created = await wire.call("CreateInput", {"sceneName": "ArParity", "inputName": "Background", "inputKind": "ffmpeg_source", "inputSettings": {"local_file": str(work / "background-video.mp4"), "is_local_file": True, "looping": True}, "sceneItemEnabled": True}) + await wire.call("SetSceneItemIndex", {"sceneName": "ArParity", "sceneItemId": created["sceneItemId"], "sceneItemIndex": 0}) + await wire.call("SetInputMute", {"inputName": "Background", "inputMuted": True}) + await caption("NVIDIA / remplacement d'arriere-plan\nFond VIDEO en boucle / personne detouree native") + await asyncio.sleep(1) + first = await image("ArParity") + await asyncio.sleep(4.3) + second = await image("ArParity") + report["videoBackground"] = {"media": await wire.call("GetMediaInputStatus", {"inputName": "Background"}), "backgroundDifference": float(np.abs(first.astype(float)-second)[transparent].mean()), "subjectDifference": float(np.abs(first.astype(float)-second)[opaque].mean())} + assert report["videoBackground"]["media"]["mediaState"] == "OBS_MEDIA_STATE_PLAYING" and report["videoBackground"]["backgroundDifference"] > 1 and report["videoBackground"]["subjectDifference"] < .1, report["videoBackground"] + await wire.call("RemoveSourceFilter", {"sourceName": "Portrait", "filterName": "segment"}) + await wire.call("RemoveInput", {"inputName": "Background"}) if record_video: await asyncio.sleep(2) report["nativeRecording"] = (await wire.call("StopRecord"))["outputPath"] - report["checks"] = ["cold allocation and processing only on render demand", "idle processing stops and resumes", "actual AR registration", "face-follow and native zoom at two positions", "eye redirection same-frame comparison", "native render windows", "exact bypass", "cleanup and graceful shutdown"] + report["checks"] = ["cold allocation and processing only on render demand", "idle processing stops and resumes", "actual AR registration", "face-follow and native zoom at two positions", "eye redirection same-frame comparison", "stacked AR effects", "segmented portrait over authored background in native Program", "native render windows", "exact bypass", "cleanup and graceful shutdown"] base.exercise = exercise if __name__ == "__main__":