使用C#从C++ ASIO库录制音频流

5
我需要找到记录音频流的最佳方法。我已经用 C ++ 构建了底层代码,并将其中的部分接口与 C# 集成。

因此,我有一个 C++ 回调函数,可以为我提供一个浮点数数组的数组 - 音频信号。

目前,我的 C++ 库正在直接将数据录制到 wav 格式的文件中,并在录制结束时通知我的 C# 应用程序。

但是,我希望在 UI 界面上获得更多的交互性,例如“无限”进度条、已记录的数据量、取消按钮等等,而且由于最坏情况下只有一分钟左右,也许最好将其保留在内存中。我对 .NET 和 C# 的内存管理知之甚少,所以不知道如何有效地实现它。

在 C# 中是否有快速可调整大小的容器,可以仅将数据放入其中,稍后像数组一样访问它?

我还想根据它构建波形图像。我已经在 C++ 中完成了这些事情,但是某种程度上,我不喜欢编写太多的消息传递、传输对象等等。

把事情放在一起:

  1. 我有一个 C++ 未管理的回调函数,执行某些操作,从中调用 C# 方法一旦处理完数据,C 原型将是:

    void process(float **signal, int n); (通常为 [2][n] - 用于立体声)

    相应的 C# 是什么,如何从 C++ 回调中调用它?

  2. 最好的类来连续写入流(例如 mem.put(float[][] data, int size)),然后使用其他简单方法(例如从中保存 wav 文件或制作波形位图)将其读取为数组。

  3. 如果在 C# 中执行此操作,是否会有显着的性能损失?(托管的 C++ 包装器调用 C# 函数等等...以及可能的一些 DSP 物品)还是我只是多虑了? :)

干杯

pablox

我的解决方案

好的,我这样解决了:

在我的 C++ 头文件中,我有一个传输结构:

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

然后在托管 C++ 类中,我声明了:
delegate void       GetAudioData([In, Out] CVAudio^ audio);

然后我可以将它作为参数传递给初始化音频的方法:
void                initializeSoundSystem(void *HWnd, GetAudioData ^audio);

那个代表也有一个C的原型。
typedef void (CALLBACK *GETAUDIODATA)(CVAudio ^a);

这在内部的 C++ 类中用作:
void                initializeSoundSystem(HWND HWnd, GETAUDIODATA audio);

第一个方法的主体是:
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);
}

audioObserver的主体仅将回调存储在对象中并执行一些与音频相关的操作。
然后,在处理方法中调用回调,像这样:
            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);

而 C# 委托的主体:

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);
            }));
        }
    }
}

所以可能这不是最优秀的代码 - 我不确定。只能说它能够工作,似乎也没有泄漏等问题。:)


2
一个值得赞赏的努力,但你考虑过 http://code.google.com/p/sharpdx/ 吗? - Ian Mercer
1
我没有使用DirectX,而是ASIO。我已经编写了所有关于声音的代码,只是担心录制。我不想使用第三方库。无论如何,谢谢您的回复 :) - pablox
@pablox,只是出于好奇,你使用什么与ASIO驱动程序通信?你自己的DLL吗?据我所知,.NET可用于ASIO的唯一库是BASS.net(http://www.un4seen.com/)。 - Brad
我正在开发一个新版本的音频应用程序,这个应用程序是我几年前编写的,但是我在处理UI方面遇到了困难(它是一个纯C++ Win32应用程序,具有我的自定义控件等)。大约一个月前,我尝试了WPF并且非常喜欢它,所以我将我编写的所有音频内容提取到了一个C++/cli dll中,因此现在可以与.NET一起使用。我已经找到了我的问题的答案,所以我继续努力工作。在ASIO通信方面,我正在使用由Steinberg提供的代码包装我的自己的“驱动”架构,因此我可以以同样的方式使用DirectX(但我对此没有兴趣)。 - pablox
1个回答

2
问题1:您想将符合非托管C签名的C#委托传递到C++中。然后,您可以回调此委托,就像它是一个C函数指针一样。例如,
delegate void ASIOCallback(IntPtr signal, int n);

您需要手动将信号内存转换为托管缓冲区,或将其复制到非托管缓冲区中。这两种方法都可以使用Marshal类上的方法来完成。这引出了第二个问题。

问题2:您有几个选择,主要取决于您想将数据存储在托管内存还是非托管内存中。将其存储在非托管内存中可能更容易与非托管代码交互,并且理论上可能更有效率,因为可以减少数据副本的数量。但是,将其存储在托管内存中(通过数组、通用集合MemoryStream等)会使从托管代码访问它变得更加容易。

问题3:你需要考虑的两个性能因素是原始数字计算和数据复制。一般来说,C#中的数值操作并不比C/C++慢多少。有多个好的网络资源可用于比较这种性能。至于复制数据,我认为这可能会给你带来更多的问题。这是因为复制数据所花费的时间以及应用程序将使用的额外内存。你需要考虑是否想要在托管和非托管内存中都有数据的副本,我猜想这将取决于你想在C#和/或C++中访问它的程度。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接