1
votes

I'm coding a soundboard using NAudio as a library to manage my audio. One of the features I want to implement is the ability to be continuously recording one (or many) audio inputs and be able, at any time to save them.

The way I see this possible is by keeping a circular buffer of the last ex: 5s of samples picked up by the audio input.

I also want to avoid keeping all the data up to the point when it started as I dont want to overuse memory.

I've tried many approches to this problem:

  1. Using a circular buffer and feed it with data from "DataAvailable" event;
  2. Using "Queue" and "BufferedWaveProvider" to add the buffer data and transform it into a sample.
  3. I tried using 2 "BufferedWaveProvider" and alternating wich was getting filled depending on which was full.
  4. I also tried to use 2 wave inputs and timers to alternate which was recording.

I tried using an array of bytes and use it as a circular buffer. I filled the buffer using the "DataAvailable" event from "WaveInEvent". The "WaveInEventArgs" has a buffer so I added the data from it to the circular buffer.

private int _start = 0, _end = 0;
private bool _filled = false;
private byte[] _buffer; // the size was set in the constructor
                        // its an equation to figure out how many samples
                        // a certain time needs.

private void _dataAvailable(object sender, WaveInEventArgs e)
{

    for (int i = 0; i < e.BytesRecorded; i++)
    {
        if (_filled)
        {
            _start = _end + 1 > _buffer.Length - 1 ? _end + 1 : 0;
        }
        if (_end > _buffer.Length - 1 && !_filled) _filled = true;
        _end = _end > _buffer.Length - 1 ? _end + 1 : 0;

        _buffer[_end] = e.Buffer[i];
    }
}

Some of the attempts i made kind of worked but, most of the time, they would work for the first 5 seconds (I am aware that using a "BufferredWaveProvider" can cause that issue. I think that part of the problem is that there is a certain amount of data that is required at the beginning of the buffer, and as soon as the buffer starts overwriting that data, the audio player doesn

Another very possible cause of the problem is that i'm just starting to use NAudio and don't quite understand it fully yet.

I've been stuck with this issue for a while now and I appreciate all the help anyone can give me.

I have some more code that I could add, but I thought this question was already getting long.

Thank you in advance!

2
There is still the problem of keeping only the necessary data. I don't want to keep the "WaveIn" data up to the point where it started. I think it would need to be restarted every now and then unless it eventually stops and clears its buffer. So, is it an issue to have the "WaveIn" recording constantly or does it manage itself?Civelier Algon
I don't think that the WaveInEvent keeps any data. Check the description of the class and/or documentation. Also make sure to dispose both wave in and out objects (if they are disposable). Other than that... it's okay.GregorMohorko

2 Answers

1
votes

If anyone else wants to do something similar, I'm leaving the whole class. Use it as you want.

using System;
using NAudio.Wave;
using System.Diagnostics;

public class AudioRecorder
{
    public WaveInEvent MyWaveIn;
    public readonly double RecordTime;

    private WaveOutEvent _wav = new WaveOutEvent();
    private bool _isFull = false;
    private int _pos = 0;
    private byte[] _buffer;
    private bool _isRecording = false;

    /// <summary>
    /// Creates a new recorder with a buffer
    /// </summary>
    /// <param name="recordTime">Time to keep in buffer (in seconds)</param>
    public AudioRecorder(double recordTime)
    {
        RecordTime = recordTime;
        MyWaveIn = new WaveInEvent();
        MyWaveIn.DataAvailable += DataAvailable;
        _buffer = new byte[(int)(MyWaveIn.WaveFormat.AverageBytesPerSecond * RecordTime)];
    }

    /// <summary>
    /// Starts recording
    /// </summary>
    public void StartRecording()
    {
        if (!_isRecording)
        {
            try
            {
                MyWaveIn.StartRecording();
            }
            catch (InvalidOperationException)
            {
                Debug.WriteLine("Already recording!");
            }
        }

        _isRecording = true;
    }

    /// <summary>
    /// Stops recording
    /// </summary>
    public void StopRecording()
    {
        MyWaveIn.StopRecording();
        _isRecording = false;
    }

    /// <summary>
    /// Play currently recorded data
    /// </summary>
    public void PlayRecorded() 
    {
        if (_wav.PlaybackState == PlaybackState.Stopped)
        {
            var buff = new BufferedWaveProvider(MyWaveIn.WaveFormat);
            var bytes = GetBytesToSave();
            buff.AddSamples(bytes, 0, bytes.Length);
            _wav.Init(buff);
            _wav.Play();
        }

    }

    /// <summary>
    /// Stops replay
    /// </summary>
    public void StopReplay()
    {
        if (_wav != null) _wav.Stop();
    }

    /// <summary>
    /// Save to disk
    /// </summary>
    /// <param name="fileName"></param>
    public void Save(string fileName)
    {
        var writer = new WaveFileWriter(fileName, MyWaveIn.WaveFormat);
        var buff = GetBytesToSave();
        writer.Write(buff, 0 , buff.Length);
        writer.Flush();
    }


    private void DataAvailable(object sender, WaveInEventArgs e)
    {

        for (int i = 0; i < e.BytesRecorded; ++i)
        {
            // save the data
            _buffer[_pos] = e.Buffer[i];
            // move the current position (advances by 1 OR resets to zero if the length of the buffer was reached)
            _pos = (_pos + 1) % _buffer.Length;
            // flag if the buffer is full (will only set it from false to true the first time that it reaches the full length of the buffer)
            _isFull |= (_pos == 0);
        }
    }

    public byte[] GetBytesToSave()
    {
        int length = _isFull ? _buffer.Length : _pos;
        var bytesToSave = new byte[length];
        int byteCountToEnd = _isFull ? (_buffer.Length - _pos) : 0;
        if (byteCountToEnd > 0)
        {
            // bytes from the current position to the end
            Array.Copy(_buffer, _pos, bytesToSave, 0, byteCountToEnd);
        }
        if (_pos > 0)
        {
            // bytes from the start to the current position
            Array.Copy(_buffer, 0, bytesToSave, byteCountToEnd, _pos);
        }
        return bytesToSave;
    }

    /// <summary>
    /// Starts recording if WaveIn stopped
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Stopped(object sender, StoppedEventArgs e)
    {
        Debug.WriteLine("Recording stopped!");
        if (e.Exception != null) Debug.WriteLine(e.Exception.Message);
        if (_isRecording)
        {
            MyWaveIn.StartRecording();
        }
    }
}
0
votes

The code inside your _dataAvailable method is strange to me. I would simply write bytes from start to end and then again from the start and so forth. And then when you want to get the actual bytes to save them, create a new array that goes from the current position to the end and from the start to the current position. Check my code below.

private int _pos = 0;
private bool _isFull = false;
private byte[] _buffer; // intialized in the constructor with the correct length

private void _dataAvailable(object sender, WaveInEventArgs e)
{
    for(int i = 0; i < e.BytesRecorded; ++i) {
        // save the data
        _buffer[_pos] = e.Buffer[i];
        // move the current position (advances by 1 OR resets to zero if the length of the buffer was reached)
        _pos = (_pos + 1) % _buffer.Length;
        // flag if the buffer is full (will only set it from false to true the first time that it reaches the full length of the buffer)
        _isFull |= (_pos == 0);
    }
}

public byte[] GetBytesToSave()
{
    int length = _isFull ? _buffer.Length : _pos;
    var bytesToSave = new byte[length];
    int byteCountToEnd = _isFull ? (_buffer.Length - _pos) : 0;
    if(byteCountToEnd > 0) {
        // bytes from the current position to the end
        Array.Copy(_buffer, _pos, bytesToSave, 0, byteCountToEnd);
    }
    if(_pos > 0) {
        // bytes from the start to the current position
        Array.Copy(_buffer, 0, bytesToSave, byteCountToEnd, _pos);
    }
    return bytesToSave;
}