Wednesday 12 September 2012

ASIO Recording in NAudio

It’s been a long time coming, but I’ve finally checked in support for ASIO recording in NAudio. It’s not actually too difficult to implement, but the main problem has been finding the time for it, and how best to present the feature within the NAudio API. I decided in the end to just do the simplest thing that works. This means simply extending the AsioOut class to allow you to optionally record at the same time as playing with NAudio.

To initiate ASIO Recording, there is a new InitRecordAndPlayback method, which allows you to specify how many channels to record. If you are only recording, and not playing back as well, then you need to tell it what sample rate you would prefer in a third parameter (leaving the input IWaveProvider null).

this.asioOut.InitRecordAndPlayback(null, recordChannelCount, 44100);

I’ve also added an InputChannelOffset property, which means you can skip over some of the input channels to select just the input you want. Obviously in the future it would be better to let you pick explicitly which inputs you want to record from.

To start recording (and playback), you simply call the Play method, and you must explicitly stop recording yourself. You are alerted of new audio available via the AudioAvailable event. This gives you an AudioAvailableEventArgs which gives you direct access to the ASIO driver’s record buffers for maximum performance, along with AsioSampleType which tells you what format the audio is in:

public class AsioAudioAvailableEventArgs : EventArgs
{

    /// <summary>
    /// Pointer to a buffer per input channel
    /// </summary>
    public IntPtr[] InputBuffers { get; private set; }

    /// <summary>
    /// Number of samples in each buffer
    /// </summary>
    public int SamplesPerBuffer { get; private set; }

    /// <summary>
    /// Audio format within each buffer
    /// Most commonly this will be one of, Int32LSB, Int16LSB, Int24LSB or Float32LSB
    /// </summary>
    public AsioSampleType AsioSampleType { get; private set; }

    /// <summary>
    /// Converts all the recorded audio into a buffer of 32 bit floating point samples, interleaved by channel
    /// </summary>
    /// <returns>The samples as 32 bit floating point, interleaved</returns>
    public float[] GetAsInterleavedSamples() ...
}

The GetAsInterleavedSamples is a helper method to make working with the recorded audio easier. It creates a float array, and reads the recorded samples into that, converting from 16 or 24 bit if necessary (only the most common ASIO sample types are supported). This saves you from having to write your own unsafe C# code if you are not comfortable with that. Remember that this callback is happening from within the ASIO driver itself, so you must return as quickly as possible. If you crash while in this callback you could find your ASIO driver fails and you may need to reboot your computer to recover the audio device.

I’ve updated the NAudioDemo application to include a simple demonstration of ASIO recording in action, so if you can, test it out with your soundcard and let me know how you get on. I am aware that the public interface for this feature is a bit of a hack, but time does not permit me at the moment to polish it further. Hopefully a future version of NAudio will feature an improved ASIO API, but for NAudio 1.6 there will at least be a way to do recording with ASIO.

8 comments:

Anonymous said...

Hi Mark, Im using ASIO for recording and was wondering if I could access the buffer at anytime, not waiting for the AudioAvailable event to trigger. Thanks in advance.

Unknown said...

I wouldn't recommend doing that. I guess you could try it and see what happens, but you risk playing garbled audio or even crashing your soundcard driver.

Unknown said...

very nice code........

Anonymous said...

Hey Mark. This is Carl Franklin from .NET Rocks (www.dotnetrocks.com). I'm also an audio engineer. Here's my studio (www.pwop.com).

I'm trying to make a routing app for ASIO. I want to be able to route an input to an output.

I see that I might be able to do this by recording data from one input and sending it to an output.

Do you have any samples of that?

Carl
carl@franklins.net

Unknown said...

hey Carl, great to hear from you. I'm a long-time listener to .NET rocks. I dabble a bit in home studio recording myself, so getting ASIO up and running with NAudio is something I've wanted for ages.

The awkward thing about ASIO and NAudio is that an ASIO device presents itself in a very different way to most other Windows audio devices. Other devices appear as playback and record devices that you open separately. So my approach with them is to put the audio received into a BufferedWaveProvider and then let the output device play from that.

However, ASIO has a much more elegant model which is well suited for low latency recording situations. You basically get one callback in which you get to examine the record buffers and write to the output buffers. It has not been easy to shoehorn the separate In and Out devices approach of NAudio to support this. However, that approach still ought to work, and with the aid of the MultiplexingWaveProvider, you ought to be able to handle patching from any input to output.

A different way to tackle your problem would be to take a copy of the AsioOut code and modify it slightly. Then, in the driver_BufferUpdate callback, instead of raising the audioAvailable event and reading from waveStream, just copy directly from the relevant inputChannel to outputChannel. The bit depth of samples will be the same in both so it keeps things nice and simple. I'd also zero out the channels you weren't using just to be on the safe side.

I'm afraid I haven't got a demo of this, but if I can find some free time maybe I'll have a go at it. I've only very recently got my first audio interface with more than two outputs (Focusrite 2i4), so I can finally test this properly myself.

On the performance front, the killer is the .NET garbage collector. You have to hope that it doesn't kick in during a callback. With ASIO you often run at super low latencies (<10ms), and the garbage collector can cause a callback to be missed, meaning you'd hear a glitch in the audio output.

Unknown said...

Hi Mark i'm wondering if i can use ASIO to record from a live radio for a precise periods of time and save it as an MP3 format.

Unknown said...

Hi Mark i'm wondering if i can use ASIO to record from a live radio for a precise periods of time and save it as an MP3 format.

Unknown said...

what do you mean by live radio? ASIO is for use with professional soundcards, and you are typically recording via one of the analogue inputs rather than say from internet radio.