【问题标题】:Underrun in Oboe/AAudio playback stream双簧管/AAudio 播放流中的欠载
【发布时间】:2018-11-26 10:05:26
【问题描述】:

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

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

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

【问题讨论】:

    标签: android audio android-ndk oboe


    【解决方案1】:

    有时,我需要向设备发送数据

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

    ->setFramesPerCallback(1024);

    您需要 1024 吗?那是FFT吗?如果没有,那么如果没有指定 FramesPerCallback,AAudio 可以更好地优化回调。

    在所有双簧管示例中,回调都注册在输出流上, 并且阅读被阻塞。这有重要性吗?

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

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

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

    使用输出回调,您应该将输入耗尽一段时间,以便它运行接近空。

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

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

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

    【讨论】:

    • 谢谢! 1024 帧现在是一个任意的数字,但我们使用的数据处理算法需要在少量数据下正常工作。 1ns 超时只是我忘记删除的一个测试,但它仍然不适用于 0。
    【解决方案2】:

    根据 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多