SHA256
56 lines
2.6 KiB
C#
56 lines
2.6 KiB
C#
using System;
|
|
|
|
namespace ADOFAIRenderer.Util.Audio;
|
|
|
|
/// <summary>
|
|
/// 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 (<see cref="RationalUtil.Double2MaxPrecisionInt"/>).
|
|
/// 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).
|
|
/// </summary>
|
|
internal class AudioSyncCalculator
|
|
{
|
|
/// <summary>Audio sample rate in Hz (samples per channel per second).</summary>
|
|
private readonly long _sampleRate;
|
|
|
|
/// <summary>Video frame rate as a fraction (frames per second = Numerator / Denominator).</summary>
|
|
private readonly (long Numerator, long Denominator) _fpsRational;
|
|
|
|
/// <summary>Total samples handed out since construction or <see cref="Reset"/> (kept for debugging, not read).</summary>
|
|
// ReSharper disable once NotAccessedField.Local
|
|
private long _currentSample;
|
|
|
|
/// <summary>Remainder of the last division, in units of 1 / Numerator samples (0 ~ Numerator - 1).</summary>
|
|
private long _remainder;
|
|
|
|
/// <param name="sampleRate">Audio sample rate in Hz.</param>
|
|
/// <param name="fps">Video frame rate in frames per second (the encoded frame rate).</param>
|
|
public AudioSyncCalculator(long sampleRate, double fps)
|
|
{
|
|
_sampleRate = sampleRate;
|
|
_fpsRational = RationalUtil.Double2MaxPrecisionInt(fps);
|
|
}
|
|
|
|
/// <summary>Restarts the sequence at frame 0 (the remainder is cleared).</summary>
|
|
public void Reset()
|
|
{
|
|
_currentSample = 0;
|
|
_remainder = 0;
|
|
}
|
|
|
|
/// <summary>Returns the sample count (frames per channel) of the next video frame and advances to the following frame.</summary>
|
|
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;
|
|
}
|
|
} |