Class RealtimeCaptureMixer
Live-mixes several capture sources (microphones, WASAPI loopback, etc.) that may each have a different sample rate and channel count into a single stream in a common target format. Each source is wrapped in a CaptureMixerInput, and the mixed output is paced to the wall clock (anchored to the first captured sample) so its length matches real elapsed time. That pacing is what keeps independently-clocked sources in sync: a source running slightly slow is padded and one running slightly fast is drained, and a loopback source that stops delivering while nothing is playing simply contributes silence until it resumes.
public class RealtimeCaptureMixer
- Inheritance
-
RealtimeCaptureMixer
- Inherited Members
Remarks
Typical use with WasapiRecorder:
var mixer = new RealtimeCaptureMixer(WaveFormat.CreateIeeeFloatWaveFormat(48000, 2));
var micInput = mixer.AddInput(micRecorder.WaveFormat);
micRecorder.DataAvailable += (data, flags, dev, qpc) => micInput.AddSamples(data);
// ...repeat for a loopback recorder...
mixer.Start();
micRecorder.StartRecording();
// pump on a background thread:
var buffer = new float[mixer.WaveFormat.SampleRate * mixer.WaveFormat.Channels];
while (recording) {
int n = mixer.Read(buffer, 0, buffer.Length);
if (n > 0) writer.WriteSamples(buffer, 0, n); else Thread.Sleep(5);
}
Read(float[], int, int) hands back only as many samples as the wall clock says should exist by now, so a free-running pump loop stays real-time instead of racing ahead producing silence — do not read the underlying Output mixer directly in a tight loop.
Constructors
RealtimeCaptureMixer(WaveFormat, TimeSpan?)
Creates a new mixer.
public RealtimeCaptureMixer(WaveFormat targetFormat, TimeSpan? preRoll = null)
Parameters
targetFormatWaveFormatThe mixed output format. Must be 32-bit IEEE float.
preRollTimeSpan?A small cushion of audio to build up (measured from the first captured sample, not from Start()) before output begins, so the mixer doesn't immediately underrun. Default 50ms.
Properties
Inputs
The inputs added so far.
public IReadOnlyList<CaptureMixerInput> Inputs { get; }
Property Value
Output
The underlying mixer. Exposed for advanced composition; for real-time capture read via Read(float[], int, int) instead so the output stays paced to the wall clock.
public ISampleProvider Output { get; }
Property Value
OutputFrames
Total frames read from the mixer (paced output) since Start(). Diagnostics.
public long OutputFrames { get; }
Property Value
WaveFormat
The common target format everything is mixed into (32-bit IEEE float).
public WaveFormat WaveFormat { get; }
Property Value
Methods
AddInput(WaveFormat, Func<ISampleProvider, ISampleProvider>, TimeSpan?)
Adds a capture source in its native format and returns the input to feed captured bytes
into. Optionally wrap the input's provider (e.g. with a MeteringSampleProvider for
a level meter) before it reaches the mixer via tap.
public CaptureMixerInput AddInput(WaveFormat sourceFormat, Func<ISampleProvider, ISampleProvider> tap = null, TimeSpan? bufferDuration = null)
Parameters
sourceFormatWaveFormatThe native capture format of the source.
tapFunc<ISampleProvider, ISampleProvider>Optional transform applied to the input's provider before it is added to the mixer. The mixer reads through whatever this returns, so metering/monitoring inserted here sees the audio as it is consumed.
bufferDurationTimeSpan?Internal buffer size for this input (default 2s).
Returns
Read(float[], int, int)
Reads mixed audio, paced to the wall clock: returns at most as many samples as should have been produced by now, and 0 before the first audio arrives, during the pre-roll window, or when the caller has already caught up. Write whatever it returns and sleep briefly on 0.
public int Read(float[] buffer, int offset, int maxSamples)
Parameters
Returns
- int
The number of samples (floats, i.e. frames × channels) written.
Start()
Arms (or re-arms) the mixer for a new capture. Call just before starting the recorders.
public void Start()