Как PortAudio буферизует аудиоданные в устройство через обратный вызов и как ведут себя выходные указатели? - PullRequest
0 голосов
/ 27 мая 2019

Я использую Debian 9 и PortAudio с ALSA для создания и приложения, которое может отправлять произвольные аудиоданные на 24-канальное устройство MOTU 24Ao. Устройство USB 2.0. Мое приложение успешно открывает поток для отправки данных на устройство и использует обратный вызов (PaStreamCallback) для передачи данных в выходной буфер, используя операции memcpy. Поток остается открытым до тех пор, пока приложение продолжает работать. Всякий раз, когда данные готовы к отправке, обратный вызов передает данные на устройство, если нет данных для воспроизведения, обратный вызов воспроизводит тишину.

Приложение работает отлично, но только в течение определенного периода времени (около 5 минут). После этого данные начинают воспроизводиться «смещенным и искаженным способом». В качестве примера я пытаюсь воспроизвести синусоидальную волну в канале 1 и нули на остальных 23 каналах в течение 400 мс. Это играется всякий раз, когда я нажимаю клавишу на клавиатуре. В начале все работает нормально с обратным вызовом и данные воспроизводятся на соответствующем канале, когда я нажимаю клавишу. Через 5 минут, когда я нажимаю клавишу, канал 1 и канал 9 начинают воспроизводить данные. Данные на обоих каналах - это не правильная синусоида, а неправильная реконструкция сигнала. Это продолжается примерно в течение 1 минуты, когда я продолжаю нажимать клавишу активации. По истечении этого времени синусоида начинает воспроизводиться нормально, но теперь на канале 9 и тишина на остальных 23 каналах. После другого периода времени это поведение повторяется, но теперь затрагивает каналы 9 и 17, что в итоге приводит к тому, что канал 17 воспроизводит синусоидальную волну.

Это происходит многократно, и похоже, что выходной буфер циклически отображает мои данные, то есть канал 1 преобразуется в 9, через некоторое время он преобразуется в 17, а затем обратно в 1. Сдвиг всегда происходит смещение 8 каналов (1 + 8 = 9, 9 + 8 = 17 и 17 + 8 = 1 снова). Когда я пытаюсь реализовать этот же код в Windows, такого странного сдвига не бывает.

Я попытался переключить устройства MOTU и даже попробовать чистую установку Debian, используя только PortAudio.

Сигналы, которые я играю, всегда сохраняются в плавающем буфере, который имеет матричную структуру, читаемую как линейный массив слева направо и сверху вниз. Каждая матрица имеет 24 столбца (по 1 для каждого канала) и определенное количество строк, определяемое длительностью каждого сигнала и частотой дискретизации, используемой для их создания (44,1 кГц для всех сигналов). Каждая матрица содержится в объекте Symbol, который может: определить, сколько строк матрицы осталось использовать (Symbol::remainingRows()), получить текущий индекс строки в матрице, который обозначает, какие строки осталось использовать после этого индекса (Symbol::getMatrixRowIndex()) получите указатель на все выборки в матрице, начиная с определенного индекса строки (Symbol::samplesFromRow()), и увеличьте индекс строки, который обозначает строки, которые еще должны быть использованы (Symbol::increaseIndexBy(int increase) ). С этой структурой мой PaCallback:

int MotuPlayer::paCallback(const void *inputBuffer, void *outputBuffer,
                            unsigned long framesPerBuffer,
                            const PaStreamCallbackTimeInfo* timeInfo,
                            PaStreamCallbackFlags statusFlags,
                            void *userData)
    {      
        float *out = (float*)outputBuffer;
        MotuPlayer *player = (MotuPlayer*)userData;
        Symbol* symbol = player->getCurrentPlayingSymbol();
        unsigned long i;
        (void) timeInfo; /* Prevent unused variable warnings. */
        (void) statusFlags;
        (void) inputBuffer;

        if(symbol != 0)
        {
            unsigned long rowsRemaining = symbol->remainingRows();
            if( rowsRemaining <= framesPerBuffer)
            {
                //Copy the remaining samples into the buffer
                memcpy(out, symbol->samplesFromRow((int)symbol->getMatrixRowIndex()), sizeof(float)*rowsRemaining*24);

                //Fill the rest of the buffer with zeros
                out += 24*rowsRemaining;
                memcpy(out, player->getZeros(), sizeof(float)*24*(framesPerBuffer-rowsRemaining));
                player->signalSymbolCallback(TapsNoError);
                player->currentPlayingSymbol = 0;
            }
            else
            {
                //Fill all the frames of the buffer with data from matrix and increase the index by the frames consumed
                memcpy(out, symbol->samplesFromRow((int)symbol->getMatrixRowIndex()), sizeof(float)*framesPerBuffer*24);
                symbol->increaseIndexBy(framesPerBuffer);
            }
        }
        else
        {
            memcpy(out, player->getZeros(), sizeof(float)*24*framesPerBuffer);
        }

        return paContinue;

У меня не было возможности определить причину проблемы, так как этот код не демонстрирует такое поведение в Windows. Мне интересно, использует ли PortAudio какой-либо кольцевой буфер для воспроизведения моих данных на аппаратном уровне, и может ли это быть проблемой. Мне кажется, что поведение указателя выходного буфера является источником проблемы, но на самом деле не знаю, что может быть решением.

...