Запись аудиопотока в C # из библиотеки C ++ ASIO - PullRequest
5 голосов
/ 27 сентября 2011

Мне нужно найти лучший способ для записи аудиопотока.Я уже построил код низкого уровня в C ++ и связал его части с C #.

Так что у меня есть обратный вызов C ++, который дает мне массив массивов с плавающей запятой - звуковой сигнал.На данный момент моя C ++ lib записывает данные прямо в файл в формате wav, и он просто уведомляет мое приложение C # о завершении записи.

Но я хотел бы иметь больше интерактивности на стороне пользовательского интерфейсанапример, «бесконечный» индикатор выполнения, количество записанных данных, кнопка отмены и т. д., и, в худшем случае, это будет минута, может быть, лучше сохранить ее в памяти.Я очень мало знаю об управлении памятью .NET и C #, поэтому я не знаю, как сделать это эффективно.

Есть ли в C # быстрый контейнер с изменяемым размером, где я мог бы просто поместить данные внутрь и получить доступ к ним позже?это как массив?

Я также хотел бы построить из него изображение формы волны.У меня уже есть эти вещи в C ++, но почему-то мне не нравится идея писать слишком много сообщений, передавать объекты и т. Д.

Итак, чтобы собрать все воедино:

  1. У меня есть неуправляемый обратный вызов C ++, который делает кое-что, и изнутри я хотел бы вызвать метод C # после обработки данных, прототип C будет:

    void process (float ** signal, intп);(обычно [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);

Затем я могу использовать его в качестве аргумента для метода инициализации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);
            }));
        }
    }
}

Так что, вероятно, это не лучший код - я понятия не имею.Только могу сказать, что это работает, не протекает и т.д. :) 1065 *

1 Ответ

2 голосов
/ 01 октября 2011

Вопрос 1: Вы хотите передать делегат C #, соответствующий неуправляемой сигнатуре C, в ваш C ++.Затем вы можете перезвонить, как если бы это был указатель на функцию C.Например,

delegate void ASIOCallback(IntPtr signal, int n);

Затем вам потребуется либо вручную распределить память сигналов в управляемом буфере, либо скопировать ее в неуправляемый буфер.Любой из них будет выполнен с использованием методов класса Marshal .Это приводит к вопросу 2.

Вопрос 2. У вас есть несколько вариантов выбора, в первую очередь определяемых тем, хотите ли вы сохранить данные в управляемой или неуправляемой памяти.Хранение неуправляемой памяти может упростить взаимодействие с неуправляемым кодом и теоретически может быть более эффективным за счет уменьшения количества копий данных.Однако сохранение его в управляемой памяти (через массив, универсальную коллекцию , MemoryStream и т. Д.) Значительно упростит доступ из управляемого кода.

Вопрос 3: Два соображения, которые вам необходимо учитывать в отношении производительности, - это необработанное вычисление чисел и дублирование ваших данных.В общем, числовые операции в C # не намного медленнее, чем в C / C ++.В Интернете есть много хороших источников для сравнения этого типа производительности.Что касается копирования данных, я думаю, что это может вызвать больше проблем для вас.Как из-за времени, потраченного на копирование данных, так и из-за дополнительной памяти, которую ваше приложение будет использовать.Вам нужно подумать, хотите ли вы, чтобы ваши данные находились как в управляемой, так и в неуправляемой памяти, и я предполагаю, что это будет зависеть от того, насколько вы хотите получить к ним доступ в C # и / или C ++.

...