我在C#中还很陌生,可能需要一些有关音频项目的帮助。
我的音频输入缓冲区填满时会调用一个方法。在方法中,我将其缓冲到本地float [],并将其传递给函数,在此函数中完成了一些音频处理。处理完该函数后,将操纵的float []返回给我,由Marshall.copy传递给音频输出缓冲区。它可以工作,但是很难以足够快的速度完成音频处理,将结果传回方法而又不会出现难看的故障。如果扩大音频缓冲区,它会变得更好,但在信号链中会出现无法忍受的高延迟。
一个问题是GC。我的DSP例程正在执行一些FFT,这些方法经常需要分配局部变量。我觉得这大大拖延了我的进度。
因此,我需要一种方法来分配一次(并重新访问)一些非托管内存,在整个运行时中保留此内存,并仅从方法中对其进行引用。
我发现例如:
IntPtr hglobal = Marshal.AllocHGlobal(8192);
Marshal.FreeHGlobal(hglobal);
所以我想用一个静态成员定义一个全局静态类“ Globasl”,并将该IntPtr分配给它。
Globals.mem1 = hglobal;
从任何其他方法中,我现在都可以通过 例如
int[] f = new int[2];
f[0] = 111;
f[1] = 222;
Marshal.Copy(f, 0, Globals.mem1, 2);
现在是我的问题: 如果要通过其他方法从上面的示例访问此int [],该怎么办?
感谢您的快速帮助。 似乎我有点不准确,对不起
我的音频设备驱动程序引发了一个我捕获的缓冲区已填充事件(由于我现在无法访问自己的家用台式机,所以使用伪代码)。看起来像:
void buffer (....)
{
byte[] buf = new byte[asiobuffersize];
marshall.copy(asioinbuffers, 0, buf, asiobufferlenth);
buf= manipulate(buf);
marshall.copy(buf, 0, asiooutbuffer, asiobufferlenth);
}
操纵函数正在执行从字节到浮点的一些转换,然后再进行一些数学运算(FFT),然后反向转换为字节,就像例如
private byte[] manipulate(byte[] buf, Complex[] filter)
{
float bu = convertTofloat(buf); //conversion from byte to audio float here
Complex[] inbuf = new Complex[bu.Length];
Complex[] midbuf = new Complex[bu.Length];
Complex[] mid2buf = new Complex[bu.Length];
Complex[] outbuf = new Complex[bu.Length];
for ( n....)
{
inbuf[n]= bu[n]; //Copy to Complex
}
midbuf=FFT(inbuf); //Doing FFT transform
for (n....)
{
mid2buf[n]=midbuf[n]*filter[n]; // Multiply with filter
}
outbuf=iFFT(mid2buf) //inverse iFFT transform
byte bu = convertTobyte(float); //conversion from float back to audio byte
return bu;
}
我希望在这里出现速度问题。因此,我认为,如果操作功能可以“获得”固定的非托管内存,并且该内存(一旦创建)就固定了所有这些变量(例如,Complex)并预先分配了内存,则可以解决该问题,因此我一定不能每次调用函数时创建一个新的。我首先预计到错误的FFT或数学错误的原因,但它发生在几秒钟的“尖锐”时间距离内,因此它与诸如削波之类的音频信号问题无关。我认为问题发生了,当GC进行了一些认真的工作,并且恰好让我吃掉了这几毫秒的时间,以便及时填补缓冲区的不足时。 可以接受。
答案 0 :(得分:0)
我真的怀疑您遇到的问题是由托管缓冲区创建/复制引起的。相反,我认为您的问题是您的数据捕获逻辑与DSP逻辑结合在一起。通常,捕获的数据驻留在循环缓冲区中,该数据在一段时间后会被重写,因此您应该在可能的情况下尽快获取此数据。 问题在于,直到完成DSP后,您才获取下一个可用的数据块。您已经知道FFT操作确实占用大量CPU!如果您有处理高峰,则可能无法在捕获驱动程序重写数据之前检索数据。
解决您问题的一种可能性是,尝试增加捕获缓冲区的大小和/或可用数量(如果可能)。这样可以为您腾出更多时间来重写捕获的数据。另一种可能性,也是我最喜欢的一种可能性,是将处理阶段与捕获阶段分离,这样,如果在忙于执行DSP计算时有新数据可用,您仍然可以并在它可用时几乎对其进行缓冲。您将更容易适应manipulate
方法内部由垃圾收集引起的暂停或计算峰值。
这将涉及创建两个线程:捕获线程和处理线程。您还需要一个“事件”来通知处理线程新数据可用,以及一个队列,它将用作动态可扩展缓冲区。
捕获线程如下所示:
// the following are class members
AutoResetEvent _bufQueueEvent = new AutoResetEvent(false);
Queue<byte[]> _bufQueue = new Queue<byte[]>;
bool _keepCaptureThreadAlive;
Thread _captureThread;
void CaptureLoop() {
while( _keepCaptureThreadAlive ) {
byte[] asioinbuffers = WaitForBuffer();
byte[] buf = new byte[asiobuffers.Length]
marshall.copy(asioinbuffers, 0, buf, asiobufferlenth);
lock( _bufQueue ) {
_bufQueue.Enqueue(buf);
}
_bufQueueEvent.Set(); // notify processing thread new data is available
}
}
void StartCaptureThread() {
_keepCaptureThreadAlive = true;
_captureThread = new Thread(new(ThreadStart(CaptureLoop));
_captureThread.Name = "CaptureThread";
_captureThread.IsBackground = true;
_captureThread.Start();
}
void StopCaptureThread() {
_keepCaptureThreadAlive = false;
_captureThread.Join(); // wait until the thread exits.
}
处理线程看起来像这样
// the following are class members
bool _keepProcessingThreadAlive;
Thread _processingThread;
void ProcessingLoop() {
while( _keepProcessingThreadAlive ) {
_bufQueueEvent.WaitOne(); // thread will sleep until fresh data is available
if( !_keepProcessingThreadAlive ) {
break; // check if thread is being waken for termination.
}
int queueCount;
lock( _bufQueue ) {
queueCount = _bufQueue.Count;
}
for( int i = 0; i < queueCount; i++ ) {
byte[] buffIn;
lock( _bufQueue ) {
// only lock during dequeue operation, this way the capture thread Will
// be able to enqueue fresh data even if we are still doing DSP processing
buffIn = _bufQueue.Dequeue();
}
byte[] buffOut = manipulate(buffIn); // you are safe if this stage takes more time than normal, you will still get the incoming data
// additional logic using manipulate() return value
...
}
}
}
void StartProcessingThread() {
_keepProcessingThreadAlive = true;
_processingThread = new Thread(new(ThreadStart(ProcessingLoop));
_processingThread.Name = "ProcessingThread";
_processingThread.IsBackground = true;
_processingThread.Start();
}
void StopProcessingThread() {
_keepProcessingThreadAlive = false;
_bufQueueEvent.Set(); // wake up thread in case it is waiting for data
_processingThread.Join();
}
在我的工作中,我们还执行许多DSP,这种模式确实帮助我们解决了您遇到的问题。