Table of Contents

Class RealtimeCaptureMixer

Namespace
NAudio.Extras
Assembly
NAudio.Extras.dll

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

targetFormat WaveFormat

The mixed output format. Must be 32-bit IEEE float.

preRoll TimeSpan?

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

IReadOnlyList<CaptureMixerInput>

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

ISampleProvider

OutputFrames

Total frames read from the mixer (paced output) since Start(). Diagnostics.

public long OutputFrames { get; }

Property Value

long

WaveFormat

The common target format everything is mixed into (32-bit IEEE float).

public WaveFormat WaveFormat { get; }

Property Value

WaveFormat

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

sourceFormat WaveFormat

The native capture format of the source.

tap Func<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.

bufferDuration TimeSpan?

Internal buffer size for this input (default 2s).

Returns

CaptureMixerInput

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

buffer float[]
offset int
maxSamples int

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()