Skip to content

Fail over when FFmpeg audio clock queries fail - #149

Draft
Blackspirits wants to merge 1 commit into
audit/ffmpeg-audio-winmm-integration-fa52from
audit/ffmpeg-audio-clock-health-fd98
Draft

Blackspirits wants to merge 1 commit into
audit/ffmpeg-audio-winmm-integration-fa52from
audit/ffmpeg-audio-clock-health-fd98

Conversation

@Blackspirits

@Blackspirits Blackspirits commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Purpose

Dependent FFmpeg audio-runtime follow-up to #148 ("Validate WinMM hardening on integrated FFmpeg audio core").

#147/#148 compose the audited audio pipeline and Windows sink hardening. One residual failure mode remained: WinMM and Core Audio returned the last known playback position when their native clock-query APIs failed, so the player could not distinguish a legitimate temporarily unchanged clock from an invalid clock source.

That can leave the player permanently anchored to a frozen audio master clock even while PCM writes continue to succeed.

Current upstream SubtitleEdit/subtitleedit main was rechecked at dda1fce8f9d4a605b1b6732acc072c7109dc6323; the newer upstream changes are Auto-Translate related and do not touch the FFmpeg player or audio sinks.

Finding fixed

WinMM

waveOutGetPosition() errors or unsupported returned MMTIME formats previously collapsed to _lastRawPosition with no validity signal.

Core Audio

AudioQueueGetCurrentTime() errors or non-finite sample times previously collapsed to _lastSampleTime with no validity signal.

In both cases FfmpegPlayer.Clock() could trust a frozen value indefinitely.

Clock-health contract

IAudioSink now exposes:

bool TryGetPlayedSeconds(out double playedSeconds)

The output contains the safest usable position for continuity; the boolean says whether that sink clock is healthy enough to remain the player's master clock.

  • SilentAudioSink is always valid.
  • WinMM and Core Audio keep the last known position across a transient native query error.
  • A single runtime query failure is tolerated; any successful read resets the failure count.
  • Two consecutive native query failures mark the current sink clock unhealthy.
  • A failed post-reset baseline is stricter: that serial has no trustworthy native-clock delta, so the sink returns 0 played seconds + unhealthy until a later reset establishes a fresh baseline.

The baseline rule prevents an accumulated absolute WinMM/Core Audio counter from being misinterpreted as time played since the new seek.

Player failover

When the active/current/latest serial has an anchored audio clock and TryGetPlayedSeconds reports unhealthy:

  1. use the safe reported played position;
  2. mark native audio clock/output failed;
  3. clear the audio anchor;
  4. rebase wall-clock timing without a discontinuity;
  5. keep Validate integrated FFmpeg audio core hardening stack #147 decode-only audio processing so timestamps/resampler drain/EOF remain observable;
  6. let a later seek/reset clear the failed state and attempt native recovery again.

If a newer seek is already pending, the old serial's clock failure does not take ownership from that transaction.

The write-failure path also uses TryGetPlayedSeconds, so write failure and clock-health failure converge on one failover transition.

Regression coverage

Native-free tests pin:

  • one transient clock read failure remains healthy;
  • a later valid read clears the failure count;
  • two consecutive failures become unhealthy;
  • an invalid reset baseline remains unhealthy even with zero runtime-failure count;
  • clock failure is promoted only for the current/latest anchored serial and outside close;
  • stale anchor/newer requested serial is not promoted;
  • SilentAudioSink always reports a valid bounded clock;
  • existing write-failure/failover-position tests remain intact.

Scope / branch state

Acceptance

AI assistance: ChatGPT was used for native-clock failure modelling, post-reset baseline analysis, transient-error policy design, seek/failover ordering review, WinMM/Core Audio reconciliation and focused regression design.

Final CI

Authoritative run: SubtitleEdit#307 on ee6ca3a2a1aebdd4f2be7871cc3b61bb1cdcab39

  • SeConv: 488 passed / 2 skipped / 0 failed
  • LibUiLogic: 905 passed / 0 skipped / 0 failed
  • LibSE: 2017 passed / 0 skipped / 0 failed
  • UI: 5297 passed / 9 skipped / 0 failed
  • build: 0 errors / 7 pre-existing warnings
  • retry step skipped
  • failure-artifact upload skipped

Final adversarial review was performed on this exact HEAD after CI; no blocker found.

@Blackspirits Blackspirits left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final adversarial review on 3a408d581df214ca76f7227d8b45c919b8691faa after CI SubtitleEdit#293: no blocker found. Verified WinMM/Core Audio expose current clock-read validity while retaining the best-known position, SilentAudioSink remains always-valid, only the current/latest anchored serial can promote a clock-read failure to device failover, a pending newer seek wins instead, write-failure and clock-failure paths share the same wall-clock transition, and a later successful seek/reset can attempt native recovery. No merge/promotion performed.

@Blackspirits
Blackspirits force-pushed the audit/ffmpeg-audio-clock-health-fd98 branch from 21a37e7 to ee6ca3a Compare September 16, 2026 16:45

@Blackspirits Blackspirits left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final adversarial review on ee6ca3a2a1aebdd4f2be7871cc3b61bb1cdcab39 after CI SubtitleEdit#307: no blocker found. Verified invalid reset baselines fail safe at zero played delta until a later reset, one transient native clock-query failure is tolerated, two consecutive failures mark the clock unhealthy, successful reads recover the transient-failure count, only the current/latest anchored serial can trigger failover, and wall-clock rebasing preserves continuity without trusting an absolute native counter as a post-seek delta. No merge/promotion performed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant