Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recording an audio stream in C# from C++ ASIO library

Tags:

c++

c#

wpf

audio

asio

I need to find the best way to record an audio stream. I have already built the low level code in C++ and interfaced parts of it to C#.

So i have a C++ callback that gives me an array of array of floats - the audio signal. At the moment, my C++ lib is recording the data straight to the file in the wav format and it just notify my C# app when it ends recording.

But, I would like to have more interactivity on the UI side, like 'infinite' progress bar, amount of the data recorded, cancel button etc, and since it's gonna be a minute at worst, maybe it's better to keep it in memory. I know very little about .NET and C# memory management, so i don't know how to make it efficiently.

Is there any fast resizable container in C#, where i could just put the data inside and later access it like an array?

I also would like to build a waveform image off it. I already have those things done in C++, but somehow i don't like the idea to write too much messaging, transfer objects etc.

So to put things together:

  1. I have C++ unmanaged callback that does some stuff and from within i'd like to call C# method once it has processed the data, the C prototype would be:

    void process(float **signal, int n); (usually [2][n] - for stereo)

    What would be C# equivalent and how do i call it from that C++ callback ?

  2. What is the best class to write continuous stream to (like mem.put(float[][] data, int size) ) and then read it as an array or with other easy way (to save a wav file from it for example or make a waveform bitmap)

  3. Is there going to be a significant performance loss if i do it in C#? (managed c++ wrapper calling c# function etc... and probably some DSP stuff) or i am just paranoid ? :)

Cheers,

pablox

My solution

Ok i solved it that way:

In my C++ header file i got transfer structure:

public ref struct CVAudio {
   public:
    float *left;
    float *right;
    int length;
   };

Then in managed C++ class i have declared:

delegate void       GetAudioData([In, Out] CVAudio^ audio);

Then i can use it as an argument to the method that initialize audio:

void                initializeSoundSystem(void *HWnd, GetAudioData ^audio);

That delegate has also a C prototype of

typedef void (CALLBACK *GETAUDIODATA)(CVAudio ^a);

Which is used in the internal C++ class as:

void                initializeSoundSystem(HWND HWnd, GETAUDIODATA audio);

Then body of the first method is:

void VDAudio::initializeSoundSystem(void *HWnd, GetAudioData ^audio) 
{
    HWND h = (HWND) HWnd;

    acb = audio;
    pin_ptr<GetAudioData ^> tmp = &audio;

    IntPtr ip = Marshal::GetFunctionPointerForDelegate(audio);
    GETAUDIODATA cb = static_cast<GETAUDIODATA>(ip.ToPointer());

    audioObserver->initializeSoundSystem(h, cb);
}

Body of audioObserver just stores that callback in the object and do some audio related things.

Callback is then called in the processing method like that:

            VDAudio^ a = gcnew VDAudio();
            a->left = VHOST->Master->getSample()[0]; //returns left channel float*
            a->right = VHOST->Master->getSample()[1];
            a->length = length;
            (*callback)(a);

And the body of C# delegate:

public void GetSamples(CVAudio audio)
{
    unsafe
    {

        float* l = (float*)audio.left;
        float* r = (float*)audio.right;

        if (l != null)
        {

            SamplePack sample = new SamplePack();

            sample.left = new float[audio.length];
            sample.right = new float[audio.length];

            IntPtr lptr = new IntPtr((void*)l);
            IntPtr rptr = new IntPtr((void*)r);

            Marshal.Copy(lptr, sample.left, 0, audio.length);
            Marshal.Copy(rptr, sample.right, 0, audio.length);

            this.Dispatcher.Invoke(new Action(delegate()
            {
                GetSamples(sample);
            }));
        }
    }
}

So probably that's not a best code around - i have no idea. Only can say it works, doesn't seem to leak etc. :)

like image 920
pablox Avatar asked Nov 04 '22 13:11

pablox


1 Answers

Question 1: You want to pass a C# delegate matching the unmanaged C signature into your C++. You can then call back on this as if it was a C function pointer. For example,

delegate void ASIOCallback(IntPtr signal, int n);

You would then need to either manually marshal the signal memory into a managed buffer, or copy it into a unmanaged buffer. Either would be done using methods on the Marshal class. This leads into question 2.

Question 2: You have a couple of choices, primarily determined by whether you want to store the data in managed or unmanaged memory. Storing it unmanaged memory may make it easy to interact with you unmanaged code and could theoretically be more efficient by reducing the number of copies of the data. However, storing it in managed memory (via an array, generic collection, MemoryStream, etc) would make it much easier to access from managed code.

Question 3: The two considerations you need to make regarding performance are raw number crunching and duplicating you data. In general, numerical operations in C# are not much slower than C/C++. There are multiple good sources on the web for comparing this type of performance. As far as copying data, I think this may cause more of an issue for you. Both due to the time spent copying the data and the extra memory you application will use. You need to consider whether you want a copy of your data in both managed and unmanaged memory, and I am guessing this will be driven by how much you want to access it in C# and/or C++.

like image 176
joncham Avatar answered Nov 09 '22 16:11

joncham