Decode audio in C# from a stream that can’t seek, such as a network response or a pipe, block by block with flat memory, with limits and typed errors for untrusted input.
using System.Diagnostics; using OneAudio; // A stream that can't seek, like a network response or a pipe: here the // output of `cat`. var cat = Process.Start(new ProcessStartInfo("cat", "music.ogg") { RedirectStandardOutput = true, })!; Stream pipe = cat.StandardOutput.BaseStream; Console.WriteLine($"pipe: CanSeek {pipe.CanSeek}"); // Limits for input you don't trust. Sizes read from the file are checked // before anything is allocated. var limits = new AudioLimits { MaxChannels = 2, MaxSampleRate = 48000 }; using var cancel = new CancellationTokenSource(TimeSpan.FromSeconds(30)); using IAudioDecoder decoder = Audio.Open(pipe, leaveOpen: false, limits, cancel.Token); int channels = decoder.Format.Channels; Console.WriteLine($"{decoder.Format.SampleRate} Hz, {channels} ch, CanSeek {decoder.CanSeek}"); // Block by block: memory use doesn't grow with the length of the file. float[] block = new float[1024 * channels]; long frames = 0; double sumSquares = 0; int read; while ((read = decoder.Read(block)) > 0) { foreach (float s in block.AsSpan(0, read * channels)) sumSquares += s * s; frames += read; } double rms = 10 * Math.Log10(sumSquares / (frames * channels)); Console.WriteLine($"{frames} frames, RMS {rms:F1} dBFS"); try { decoder.Seek(0); } catch (NotSupportedException) { Console.WriteLine("Seek on a pipe: NotSupportedException"); } // A file over the limits is refused with an exception that names the limit. try { using IAudioDecoder tooBig = Audio.Open("music.wav", new AudioLimits { MaxSampleRate = 32000 }); } catch (AudioLimitExceededException e) { Console.WriteLine(e.Message); } // Bytes that aren't audio at all. try { using IAudioDecoder notAudio = Audio.Open(new MemoryStream("not audio"u8.ToArray())); } catch (UnsupportedAudioFormatException e) { Console.WriteLine($"{e.GetType().Name}: {e.Message}"); }
pipe: CanSeek False 44100 Hz, 2 ch, CanSeek False 1411200 frames, RMS -24.8 dBFS Seek on a pipe: NotSupportedException MaxSampleRate limit exceeded: input needs 44100, limit is 32000. UnsupportedAudioFormatException: No registered container format recognises this data.
How it works
Audio.Open(stream)finds the format in the first 512 bytes. A stream that can’t seek is read through a small replay buffer, so detection works on pipes and sockets too.- The decoder reads only as much of the stream as each
Readneeds. Memory doesn’t grow with the length of the file, and no decoder allocates per read. CanSeekis false for a stream that can’t seek, andSeekthrowsNotSupportedException.LengthFramesis −1 when the length can’t be known in advance.leaveOpen: falsedisposes the stream with the decoder.
Untrusted input
AudioLimitscaps the channel count, sample rate, packet and frame sizes, metadata, codec setup data and container elements. Every size read from the input is checked before the allocation it guards.AudioLimits.Defaultapplies when you pass none.- Every error about the data is a
OneAudioException.AudioFormatExceptionmeans malformed data.UnsupportedAudioFormatException, a subclass, means valid data in a format that isn’t registered or supported.AudioLimitExceededExceptionnames the limit, the maximum and what the file asked for. - Apart from those, the library throws only argument exceptions,
NotSupportedException,ObjectDisposedException, the stream’s own I/O exceptions andOperationCanceledException. - Every parser and decoder is fuzzed. See Results in the README.
For the decoder interface in full, see Decoding and Errors and limits.