Мне нужно получить доступ к глобальной (неуправляемой) памяти в C # - PullRequest
0 голосов
/ 14 июня 2019

Я довольно новичок в c # и мне может понадобиться помощь в аудиопроекте.Мои аудио входные буферы вызывают метод, когда они заполнены.В методе, который я собираю, это буферизирует в локальный метод float [], передает его в функцию, где выполняется некоторая обработка звука.После обработки функция возвращает обработанный float [], который я передаю Marshall.copy, в буфер вывода аудио.это работает, но довольно сложно достаточно быстро выполнить аудиопроцессинг, чтобы передать результат обратно методу, не заканчивая уродливыми сбоями.Если я увеличу звуковые буферы, это станет лучше, но я получу невыносимую высокую задержку в цепи сигнала.Одной из проблем является сборщик мусора.Моя подпрограмма 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;
     }

здесь я ожидаю, что мойСкорость вопроса будет.Поэтому я подумал, что проблему можно решить, если бы управляющая функция могла просто «получить» фиксированный фрагмент неуправляемой памяти, где (после того, как он был создан), он фиксировал все эти переменные (например, Сложный) и предварительно выделенный список, так что я не долженсоздавать новые каждый раз, когда вызывается функция.Сначала я ожидал причину моих глюков в неправильном БПФ или математике, но это происходит в виде «резких» временных интервалов в несколько секунд, поэтому он не связан с такими проблемами звукового сигнала, как отсечение.Я думаю, что это случается, когда GC выполняет какую-то серьезную работу и съедает меня ровно столько, сколько пропущено за несколько миллисекунд, чтобы вовремя заполнить буфер вывода.приемлемый.

1 Ответ

0 голосов
/ 16 июня 2019

Я действительно сомневаюсь, что проблемы, с которыми вы сталкиваетесь, вызваны созданием / копированием вашего управляемого буфера.Вместо этого я думаю, что ваша проблема в том, что у вас есть логика захвата данных в сочетании с логикой DSP.Обычно захваченные данные находятся в кольцевом буфере, где данные перезаписываются через некоторое время, поэтому вы должны получить эти данные как можно скорее.Проблема в том, что вы не получаете следующий доступный блок данных до тех пор, пока ваш DSP не будет готов;Вы уже знаете, что операции FFT действительно сильно загружают процессор!Если у вас есть пик обработки, вы не сможете получить данные до того, как они будут перезаписаны драйвером захвата.

Одна из возможностей решить вашу проблему - попытаться увеличить, если возможно, размер и/ или доступное количество буферов захвата .Это дает вам больше времени, прежде чем перезаписанные данные перезаписываются.Другая возможность, которую я предпочитаю, это отсоединение вашей стадии обработки от вашей стадии захвата , таким образом, если новые данные доступны, когда вы заняты выполнением вычислений 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 и этот шаблон действительно помогли нам с проблемами, с которыми вы сталкиваетесь.

...