using System; namespace ADOFAIRenderer.Util.Audio; /// /// Splits the audio sample stream into per-video-frame sample counts so that audio and video stay aligned without drift. /// /// The frame rate is converted to an exact fraction Numerator / Denominator (). /// Frame k receives floor((k + 1) * sampleRate * Denominator / Numerator) - floor(k * sampleRate * Denominator / Numerator) samples: /// the remainder of each division is carried into the next frame, so the sum over any number of frames equals the exact /// sample count of that duration rounded down (for example 44100 Hz at 60 FPS gives 735 every frame; at 144 FPS three frames of 306 /// are followed by one of 307, 306.25 on average). /// Used by the render thread only (RenderManager.WriteAudioForFrame, once per frame sent to the encoder). /// internal class AudioSyncCalculator { /// Audio sample rate in Hz (samples per channel per second). private readonly long _sampleRate; /// Video frame rate as a fraction (frames per second = Numerator / Denominator). private readonly (long Numerator, long Denominator) _fpsRational; /// Total samples handed out since construction or (kept for debugging, not read). // ReSharper disable once NotAccessedField.Local private long _currentSample; /// Remainder of the last division, in units of 1 / Numerator samples (0 ~ Numerator - 1). private long _remainder; /// Audio sample rate in Hz. /// Video frame rate in frames per second (the encoded frame rate). public AudioSyncCalculator(long sampleRate, double fps) { _sampleRate = sampleRate; _fpsRational = RationalUtil.Double2MaxPrecisionInt(fps); } /// Restarts the sequence at frame 0 (the remainder is cleared). public void Reset() { _currentSample = 0; _remainder = 0; } /// Returns the sample count (frames per channel) of the next video frame and advances to the following frame. public int GetNextFrameSamples() { // samplesNeeded = floor((sampleRate * Denominator + remainder) / Numerator); the new remainder carries the fraction. var numerator = _sampleRate * _fpsRational.Denominator + _remainder; var samplesNeeded = numerator / _fpsRational.Numerator; _remainder = numerator % _fpsRational.Numerator; _currentSample += samplesNeeded; return (int)samplesNeeded; } }