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