Underrun в потоке воспроизведения Oboe/AAudio
Я работаю над приложением для Android, работающим с устройством, которое по сути является USB-микрофоном. Мне нужно прочитать входные данные и обработать их. Иногда мне нужно отправить данные на устройство (4 short
s * количество каналов, которое обычно составляет 2), и эти данные не зависят от входа.
Я использую Oboe, и все телефоны, которые я использую для тестирования, используют AAudio внизу.
Часть чтения работает, но когда я пытаюсь записать данные в выходной поток, я получаю следующее предупреждение в logcat
и ничего не написано на выходе:
W/AudioTrack: releaseBuffer() track 0x78e80a0400 disabled due to previous underrun, restarting
Вот мой обратный звонок:
oboe::DataCallbackResult
OboeEngine::onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) {
// check if there's data to write, agcData is a buffer previously allocated
// and h2iaudio::getAgc() returns true if data's available
if (h2iaudio::getAgc(this->agcData)) {
// padding the buffer
short* padPos = this->agcData+ 4 * playStream->getChannelCount();
memset(padPos, 0,
static_cast<size_t>((numFrames - 4) * playStream->getBytesPerFrame()));
// write the data
oboe::ResultWithValue<int32_t> result =
this->playStream->write(this->agcData, numFrames, 1);
if (result != oboe::Result::OK){
LOGE("Failed to create stream. Error: %s",
oboe::convertToText(result.error()));
return oboe::DataCallbackResult::Stop;
}
}else{
// if there's nothing to write, write silence
memset(this->agcData, 0,
static_cast<size_t>(numFrames * playStream->getBytesPerFrame()));
}
// data processing here
h2iaudio::processData(static_cast<short*>(audioData),
static_cast<size_t>(numFrames * oboeStream->getChannelCount()),
oboeStream->getSampleRate());
return oboe::DataCallbackResult::Continue;
}
//...
oboe::AudioStreamBuilder *OboeEngine::setupRecordingStreamParameters(
oboe::AudioStreamBuilder *builder) {
builder->setCallback(this)
->setDeviceId(this->recordingDeviceId)
->setDirection(oboe::Direction::Input)
->setSampleRate(this->sampleRate)
->setChannelCount(this->inputChannelCount)
->setFramesPerCallback(1024);
return setupCommonStreamParameters(builder);
}
Как видно в setupRecordingStreamParameters
Я регистрирую обратный вызов для входного потока. Во всех примерах Oboe обратный вызов регистрируется в выходном потоке, и чтение блокируется. Это имеет значение? Если нет, сколько кадров мне нужно записать в поток, чтобы избежать опустошения?
РЕДАКТИРОВАТЬ В то же время я нашел источник underruns. Выходной поток не считывал то же количество кадров, что и входной поток (что в ретроспективе кажется логичным), поэтому записывает количество кадров, заданное playStream->getFramesPerBurst()
исправить мою проблему. Вот мой новый обратный звонок:
oboe::DataCallbackResult
OboeEngine::onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) {
int framesToWrite = playStream->getFramesPerBurst();
memset(agcData, 0, static_cast<size_t>(framesToWrite *
this->playStream->getChannelCount()));
h2iaudio::getAgc(agcData);
oboe::ResultWithValue<int32_t> result =
this->playStream->write(agcData, framesToWrite, 0);
if (result != oboe::Result::OK) {
LOGE("Failed to write AGC data. Error: %s",
oboe::convertToText(result.error()));
}
// data processing here
h2iaudio::processData(static_cast<short*>(audioData),
static_cast<size_t>(numFrames * oboeStream->getChannelCount()),
oboeStream->getSampleRate());
return oboe::DataCallbackResult::Continue;
}
Это работает таким образом, я изменю, к какому потоку присоединен обратный вызов, если я замечу какие-либо проблемы с производительностью, а пока я буду держать это так.
1 ответ
Иногда мне нужно отправить данные на устройство
Вам всегда нужно записывать данные на выход. Обычно вам нужно написать хотя бы numFrames, а может и больше. Если у вас нет действительных данных для отправки, напишите нули. Предупреждение: в вашем блоке else вы вызываете memset(), но не записываете в поток.
-> setFramesPerCallback (1024);
Вам нужен 1024 конкретно? Это для БПФ? Если нет, то AAudio может оптимизировать обратные вызовы лучше, если не указан FramesPerCallback.
Во всех примерах Oboe обратный вызов регистрируется в выходном потоке, и чтение блокируется. Это имеет значение?
На самом деле чтение не является блокирующим. Какой бы поток не имел обратного вызова, он должен быть неблокирующим. Используйте timeoutNanos=0.
Важно использовать выходной поток для обратного вызова, если вы хотите низкую задержку. Это связано с тем, что выходной поток может обеспечивать режим с низкой задержкой только с помощью обратных вызовов, а не с прямыми write(). Но входной поток может обеспечить низкую задержку как с обратным вызовом, так и с read().
Как только потоки стабилизируются, вы можете читать или записывать одинаковое количество кадров в каждом обратном вызове. Но прежде чем он станет стабильным, вам может понадобиться прочитать или написать дополнительные кадры.
С обратным вызовом вывода вы должны на некоторое время опустошить вход, чтобы он работал близко к пустому.
При входном обратном вызове вы должны заполнить вывод на некоторое время, чтобы он был близок к полному.
write (this-> agcData, numFrames, 1);
Ваш тайм-аут на 1 наносекунду очень мал. Но Гобой все равно заблокирует. Вы должны использовать timeoutNanos 0 для неблокирующего режима.
Согласно документации Oboe, во время обратного вызова onAudioReady вы должны записать ровно количество кадров numFrames непосредственно в буфер, на который указывает *audioData. И вам не нужно вызывать функцию "запись" гобоя, а вместо этого заполнять буфер самостоятельно.
Не уверен, как работает ваша функция getAgc(), но, возможно, вы можете передать этой функции указатель audioData в качестве аргумента, чтобы избежать повторного копирования данных из одного буфера в другой. Если вам действительно нужен обратный вызов onAudioReady для запроса такого же количества кадров, вы должны установить это число при создании AudioStream, используя:
oboe::AudioStreamBuilder::setFramesPerCallback(int framesPerCallback)
Посмотрите здесь, что вам не следует делать во время обратного вызова onAudioReady, и вы обнаружите, что функция записи гобоя запрещена:https://google.github.io/oboe/reference/classoboe_1_1_audio_stream_callback.html