Oboe/AAudio 播放流中的欠载

Underrun in Oboe/AAudio playback stream

我正在开发一个 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 示例中,回调都在输出流上注册,并且读取是阻塞的。这重要吗?如果不是,我需要将多少帧写入流以避免欠载?

编辑 与此同时,我找到了欠载的根源。输出流没有读取与输入流相同数量的帧(事后看来这似乎是合乎逻辑的),因此写入 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;
}

它是这样工作的,如果我发现任何性能问题,我将更改附加回调的流,现在我将保持这种方式。

Sometimes, I need to send data the device

您总是需要将数据写入输出。通常您至少需要编写 numFrames,也许更多。如果您没有要发送的任何有效数据,则写入零。 警告:在您的 else 块中,您正在调用 memset() 但未写入流。

->setFramesPerCallback(1024);

具体需要1024吗?那是 FFT 吗?如果没有,则在未指定 FramesPerCallback 的情况下 AAudio 可以更好地优化回调。

In all the Oboe examples, the callback is registered on the output stream, and the reading is blocking. Does this have an importance?

实际上读取是非阻塞的。任何没有回调的流都应该是非阻塞的。使用 timeoutNanos=0.

如果您想要低延迟,使用输出流进行回调很重要。这是因为输出流只能提供带回调的低延迟模式,而不是直接写入()。但是输入流可以通过回调和 read()s 提供低延迟。

一旦流稳定,您就可以在每个回调中读取或写入相同数量的帧。但在稳定之前,您可能需要读取或写入额外的帧。

使用输出回调,您应该耗尽输入一段时间,使其 运行 接近于空。

使用输入回调,您应该在一段时间内填充输出,以便 运行 接近满。

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

您的 1 纳秒超时非常小。但是双簧管仍然会阻塞。对于非阻塞模式,您应该使用 0 的 timeoutNanos。

根据 Oboe 文档,在 onAudioReady 回调期间,您必须准确地将 numFrames 帧直接写入 *audioData 指向的缓冲区。而且您不必调用 Oboe "write" 函数,而是自己填充缓冲区。

不确定您的 getAgc() 函数是如何工作的,但也许您可以为该函数提供指针 audioData 作为参数,以避免必须再次将数据从一个缓冲区复制到另一个缓冲区。 如果您确实需要 onAudioReady 回调来请求相同数量的帧,那么您必须在构建 AudioStream 时设置该数字:

oboe::AudioStreamBuilder::setFramesPerCallback(int framesPerCallback)

看这里在onAudioReady回调中不应该做的事情,你会发现双簧管写函数是被禁止的: https://google.github.io/oboe/reference/classoboe_1_1_audio_stream_callback.html