Underrun в потоке воспроизведения гобой / AAudio - PullRequest
0 голосов
/ 26 ноября 2018

Я работаю над приложением для Android, работающим с устройством, которое в основном является USB-микрофоном.Мне нужно прочитать входные данные и обработать их.Иногда мне нужно отправить данные на устройство (4 short s * количество каналов, которое обычно составляет 2), и эти данные не зависят от входа.

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

РЕДАКТИРОВАТЬ Тем временем я нашел источник опустошения.Выходной поток не считывал такое же количество кадров, как входной поток (что в ретроспективе кажется логичным), поэтому запись количества кадров, заданного 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 Ответ

0 голосов
/ 28 ноября 2018

Иногда мне нужно отправить данные на устройство

Вам всегда нужно записывать данные на выход.Обычно вам нужно написать хотя бы numFrames, а может и больше.Если у вас нет действительных данных для отправки, напишите нули.Предупреждение: в вашем блоке else вы вызываете memset (), но не записываете в поток.

-> setFramesPerCallback (1024);

Вам нужно 1024 специально?Это для БПФ?Если нет, то AAudio может оптимизировать обратные вызовы лучше, если не указан FramesPerCallback.

Во всех примерах гобой обратный вызов регистрируется в выходном потоке, и чтение блокируется.Это имеет значение?

На самом деле чтение не является блокирующим.Какой бы поток не имел обратного вызова, он должен быть неблокирующим.Используйте timeoutNanos = 0.

Важно использовать выходной поток для обратного вызова, если вы хотите низкую задержку.Это связано с тем, что выходной поток может обеспечить режим с низкой задержкой только с помощью обратных вызовов, а не с прямыми write ().Но входной поток может обеспечить низкую задержку как с обратным вызовом, так и с read () s.

После стабилизации потоков вы можете читать или записывать одинаковое количество кадров в каждом обратном вызове.Но прежде чем он станет стабильным, вам может понадобиться прочитать или написать дополнительные кадры.

При обратном вызове вывода вы должны на некоторое время опустошить вход, чтобы он работал близко к пустому.

При входном обратном вызове вы должны заполнить вывод на некоторое время, чтобы он работал близко к полному.

write (this-> agcData, numFrames, 1);

Ваш тайм-аут на 1 наносекунду очень мал.Но Гобой все равно будет блокировать.Вы должны использовать timeoutNanos 0 для неблокирующего режима.

...