Troubleshooting
What happened, why it happened, and the line of code that fixes it.
Before anything else, turn the log on. It is silent by default and it will usually tell you the answer outright.
using Logger;
await OwnaudioNet.InitializeAsync(config, logLevel: Log.Level.Info);
// or flip it at any time while running
Log.LoggerLevel = Log.Level.Info;Nothing plays
Why: almost always one of four things โ the engine never started, the mixer never started, the source was never added, or something is at zero volume.
Console.WriteLine(OwnaudioNet.IsInitialized); // false โ InitializeAsync never completed
Console.WriteLine(OwnaudioNet.IsRunning); // false โ you forgot Start()
Console.WriteLine(mixer.IsRunning); // false โ mixer.Start()
Console.WriteLine(mixer.SourceCount); // 0 โ AddSource()
Console.WriteLine(mixer.MasterVolume); // 0 โ there it is
Console.WriteLine(source.State); // Stopped โ Play()
Console.WriteLine(mixer.LeftPeak); // 0 while playing โ nothing reaching the busTwo more that catch people out:
- Output went to a device nobody is listening to. Check
OwnaudioNet.GetOutputDevices()and which one is actually connected to speakers. - The file finished instantly. If
source.Durationis 0 the file didn't decode โ see the file won't load.
Crackling, clicks or stuttering
Why: the engine asked for the next block of audio and it wasn't ready. The mix thread is not keeping up with the buffer size you gave it.
Fix it in this order โ each step costs a little more latency than the last.
| Step | Do this | Costs |
|---|---|---|
| 1 | Raise bufferMultiplier to 16 or 32 at initialization | Nothing audible โ it only deepens the internal safety buffer |
| 2 | Raise AudioConfig.BufferSize from 512 to 1024 | ~10 ms more latency |
| 3 | Raise the mixer's bufferSizeInFrames to 1024 for 20+ tracks | Slightly coarser position updates |
| 4 | Look at what your own code is doing on the audio path | โ |
// 8+ sources, or 2+ VST plugins on the master bus
await OwnaudioNet.InitializeAsync(config, bufferMultiplier: 32);
OwnaudioNet.Start();
var mixer = new AudioMixer(OwnaudioNet.Engine!.UnderlyingEngine, bufferSizeInFrames: 1024);
mixer.Start();To find out whether it really is an underrun, ask:
mixer.BufferUnderrun += (_, e) =>
Console.WriteLine($"Underrun: {e.MissedFrames} frames at {e.Position}");
mixer.TrackDropout += (_, e) =>
Console.WriteLine($"{e.TrackName} fell behind: {e.Reason} ({e.MissedFrames} frames)");If you wrote a StreamingSource generator, check it for allocations first. A new, a LINQ query or an interpolated string inside the render callback is the most common cause of clicks that survive every buffer increase.
The app freezes at startup
Why: a blocking engine call on the UI thread. Opening a device is not instant โ on Linux, PulseAudio enumeration alone can take five seconds.
// โ freezes the window
OwnaudioNet.Initialize(config);
OwnaudioNet.Shutdown();
// โ
await OwnaudioNet.InitializeAsync(config);
await OwnaudioNet.ShutdownAsync();The same applies to Stop() / StopAsync(), which waits for the audio thread to join. Everything else โ Play, Seek, Volume, AddSource โ is safe to call from anywhere.
The audio interface was unplugged
Why: the device vanished mid-stream. By default the engine hops to the system default and hops back when the device returns; if you turned that off it parks in DeviceDisconnected and waits.
config.FallbackToDefaultOnDisconnect = true; // the default โ keep playing on built-in output
// Be told when the stream itself dies, instead of just going quiet
mixer.StreamFaulted += (_, e) =>
{
if (e.Kind == AudioStreamFaultKind.DeviceNotAvailable)
ShowToast("Audio device disconnected");
};
// Be told when the engine moves to a different device
OwnaudioNet.Engine!.OutputDeviceChanged += (_, e) =>
Console.WriteLine($"Now playing on {e.NewDeviceInfo.Name}");Tracks drift apart
Why: the sources were never attached to the master clock, so each one is free-running on its own decoder. They start together and slowly separate.
// Attach BEFORE Play() โ attaching afterwards does not retro-fix the drift
foreach (var src in sources)
{
src.AttachToClock(mixer.MasterClock);
mixer.AddSourcePrepared(src);
}
mixer.StartPreparedSources(0.0);If they are attached and still drift, the engine is telling you it's under pressure:
var diag = fileSource.SyncDiagnostics;
if (diag.IsRelaxed)
Console.WriteLine($"Tolerances widened {diag.AdaptiveScale:F1}ร โ the mix thread is struggling");A scale above 1.0 means the sync engine had to relax its thresholds to keep up. Treat it as the same symptom as crackling and give the mix thread more headroom.
A file won't load
Why: either the format isn't in the built-in set, or the file is damaged, or the path is wrong. A DecoderException means the decoder saw the file and gave up on it.
Handled natively, on every platform, with no extra install: MP3, FLAC, WAV (PCM/ADPCM), AAC, ALAC, MP4/M4A, OGG/Vorbis, AIFF.
try
{
var source = new FileSource(path, targetSampleRate: sr, targetChannels: ch);
if (source.Duration <= 0)
Console.WriteLine("Decoded, but empty โ the file is probably truncated.");
}
catch (DecoderException ex)
{
Console.WriteLine($"Cannot decode {path}: {ex.Message}");
}Anything outside that list โ Opus, WMA, AC3 โ will not open, and there is no fallback decoder to install. Convert the file ahead of time instead. See Decoding.
An effect does nothing
Why: three candidates, in order of likelihood.
- You added effects to a source that is already in the mixer. A raw source has no chain โ the mixer has to be holding the
SourceWithEffectswrapper, not the source inside it. Enabledis false, orMixis at 0.- The effect isn't ready. VST3 processors must have
InitializeAudioAsync()completed before they are added;IsReadytells you.
var chain = new SourceWithEffects(source);
chain.AddEffect(new ReverbEffect { Mix = 0.3f });
mixer.RemoveSource(source.Id); // the raw one comes out
mixer.AddSource(chain); // the wrapper goes inIf you'd rather see it than guess: an effect tap hands you the signal on both sides of the chain while it plays. Identical spectra means the chain really is doing nothing to the audio, which rules the first two causes in or out in one look.
using var analyzer = new EffectSpectrumAnalyzer(mixer.CreateEffectTap(source.Id));
if (analyzer.Update())
{
// dry == wet โ nothing in the chain is touching the audio
Compare(analyzer.PreMagnitudesDb, analyzer.PostMagnitudesDb);
}A VST3 plugin won't load
Why: it's an instrument, its architecture doesn't match, or its audio setup failed for the current sample rate.
VST3PluginHost host;
try { host = await VST3PluginHost.CreateAsync(path); }
catch (Exception ex) { ShowError($"Could not load the plugin: {ex.Message}"); return; }
if (!host.IsEffect)
{
host.Dispose();
ShowError(host.IsInstrument
? "That's an instrument โ only effect plugins can go in a slot."
: "Unknown plugin type.");
return;
}
if (!await host.InitializeAudioAsync(OwnaudioNet.Engine!.Config.SampleRate, maxBlockSize: 4096))
{
host.Dispose();
ShowError("The plugin could not run at this sample rate.");
return;
}A 64-bit process cannot load a 32-bit plugin, and vice versa. On Apple Silicon an Intel-only plugin will not load either.
Memory keeps growing
Why: something that owns native memory is never being released. Two usual suspects.
float[]? buffer = OwnaudioNet.Receive(out int sampleCount);
if (buffer != null)
{
try { /* use buffer[0..sampleCount-1] */ }
finally { OwnaudioNet.ReturnInputBuffer(buffer); } // never skip this
}And dispose sources you replace. Loading a new file into a track without disposing the old FileSource leaks its decoder and ring buffer every time.
mixer.RemoveSource(oldSource.Id);
oldSource.Dispose();The recording is empty or out of time
Why: recording captures what leaves the mixer. If nothing was on the bus, the file is silence. If a live take lands late, that's the hardware capture delay.
// Trim the capture delay off the front so the take lines up with the click
mixer.StartRecording("take.wav", compensateInputLatency: true);
// ...
mixer.StopRecording();
int trimmed = mixer.LastRecordingLatencyOffsetFrames; // what was droppedStopRecording() is what finalizes and closes the WAV header. Kill the process without it and you get an unplayable file.
Exceptions you may see
| Type | What it means | What to do |
|---|---|---|
DeviceException | The requested device could not be opened โ wrong ID, in exclusive use, or gone. | Fall back to OutputDeviceId = null and re-enumerate. |
DecoderException | The file could not be decoded. | Check the format against the supported list; convert the file if it falls outside. |
StreamException | The native audio stream failed to open or died. | Try a larger buffer or a different host type. |
HostApiNotAvailableException | The HostType you asked for doesn't exist on this machine. | Use EngineHostType.None and let the engine choose. |
AsioDriverNotFoundException | No ASIO driver installed. | Install the interface's driver, or use WASAPI. |
AbiVersionMismatchException | The managed package and the native library are different versions. | Clear bin/ and obj/, restore, and make sure only one OwnAudioSharp version is referenced. |
AudioEngineException | A general engine-level failure. | Turn on Log.Level.Error โ the message names the stage that failed. |
FAQ
Which package do I need?
OwnAudioSharp for desktop with everything. OwnAudioSharp.Mobile for Android and iOS. OwnAudioSharp.Basic when you only want audio in and out and care about size. MIDI is its own package, and vocal separation and MT3 are optional add-ons.
Do I need FFmpeg?
No โ and you cannot use it either. The Rust decoder handles the common formats out of the box and is the only decoder there is; the FFmpeg fallback of earlier versions was removed in 4.0.
How many sources can one mixer hold?
25 โ AudioConstants.MaxAudioSources. Past about eight, give the mix thread more headroom with bufferMultiplier.
What latency can I expect?
BufferSize รท SampleRate. 512 frames at 48 kHz is ~10.6 ms; the low-latency preset's 128 frames is ~2.7 ms. Add the driver's own hardware latency, readable from OwnaudioNet.InputLatencyFrames and OutputLatencyFrames once audio is flowing.
Can I run two mixers at once?
Yes, but one is almost always the right answer. If you do, register the one network sync should follow with OwnaudioNet.SetPrimaryAudioMixer().
Does it work with AOT and trimming?
Yes โ the managed side is AOT-compatible and trimmable, and the engine is native anyway.
Can I test without a sound card?
Yes. OwnaudioNet.Initialize(config, useMockEngine: true) gives you the whole API against a mock engine, which is how the library's own ~290 tests run in CI.
Why is my tempo change ignored?
Tempo and PitchShift are wired on FileSource. The other source types accept the value for compatibility but don't apply it. Tempo is also clamped to 0.8โ1.2.
Still stuck
Turn on Log.Level.Info, reproduce it, and open an issue with the log, your AudioConfig, and the platform. That is usually enough to spot it from the outside.