Underrun в потоке воспроизведения Oboe/AAudio

Я работаю над приложением для Android, работающим с устройством, которое по сути является USB-микрофоном. Мне нужно прочитать входные данные и обработать их. Иногда мне нужно отправить данные на устройство (4 shorts * количество каналов, которое обычно составляет 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

Другие вопросы по тегам