【问题标题】:Oboe stops rendering audio if there is too many data to be rendered如果要渲染的数据过多,双簧管会停止渲染音频
【发布时间】:2021-05-06 09:23:19
【问题描述】:

我正在尝试在我的应用程序中实现双簧管库,这样我就可以执行低延迟音频播放。我可以执行平移、播放操作、声音缩放等。我一直在问一些关于这个主题的问题,因为我对音频世界完全陌生。

现在我可以执行内部 Android 音频类(例如 SoundPool)提供的基本操作。我可以同时播放多个声音而不会出现明显的延迟。

但现在我遇到了另一个问题。所以我做了一个非常简单的应用程序,例如;屏幕上有按钮,如果用户点击此屏幕,它会播放简单的钢琴声。无论用户快速点击此按钮,它都必须能够像SoundPool 那样混合相同的钢琴音色。

我的代码可以很好地做到这一点,直到我点击按钮太多次,所以有很多音频队列要混合。

class OggPlayer;

class PlayerQueue {
private:
    OggPlayer* player;

    void renderStereo(float* audioData, int32_t numFrames);
    void renderMono(float* audioData, int32_t numFrames);
public:
    int offset = 0;
    float pan;
    float pitch;
    int playScale;

    bool queueEnded = false;

    PlayerQueue(float pan, float pitch, int playScale, OggPlayer* player) {
        this->pan = pan;
        this->playScale = playScale;
        this->player = player;
        this->pitch = pitch;

        if(this->pan < -1.0)
            this->pan = -1.0;
        else if(this->pan > 1.0)
            this->pan = 1.0;
    }

    void renderAudio(float* audioData, int32_t numFrames, bool isStreamStereo);
};

class OggPlayer {
private:
    std::vector<PlayerQueue> queues = std::vector<PlayerQueue>();
public:

    int offset = 0;
    bool isStereo;
    float defaultPitch = 1.0;

    OggPlayer(std::vector<float> data, bool isStereo, int fileSampleRate, int deviceSampleRate) {
        this->data = data;
        this->isStereo = isStereo;
        defaultPitch = (float) (fileSampleRate) / (float) (deviceSampleRate);
    }

    void renderAudio(float* audioData, int32_t numFrames, bool reset, bool isStreamStereo);
    static void smoothAudio(float* audioData, int32_t numFrames, bool isStreamStereo);

    void addQueue(float pan, float pitch, int playerScale) {
        queues.push_back(PlayerQueue(pan, defaultPitch * pitch, playerScale, this));
    };

    static void resetAudioData(float* audioData, int32_t numFrames, bool isStreamStereo);

    std::vector<float> data;
};

OggPlayer 保存解码后的 PCM 数据和defulatPitch 值,以同步扬声器的采样率和音频文件的采样率。每个OggPlayer 都拥有自己的PCM 数据(即每个音频文件的数据),并且拥有自己的PlayerQueue 向量。 PlayerQueue 是实际呈现音频数据的类。 OggPlayerPlayerQueue 类的 PCM 数据提供者。 PlayerQueue 有自己的自定义音高、平移和音频比例值。由于AudioStream 可以在回调方法中提供有限大小的数组,所以我添加了offset,因此PlayerQueue 可以在下一个会话中继续渲染音频而不会丢失其状态。

void OggPlayer::renderAudio(float *audioData, int32_t numFrames, bool reset, bool isStreamStereo) {
    if(reset) {
        resetAudioData(audioData, numFrames, isStreamStereo);
    }

    for(auto & queue : queues) {
        if(!queue.queueEnded) {
            queue.renderAudio(audioData, numFrames, isStreamStereo);
        }
    }

    smoothAudio(audioData, numFrames, isStreamStereo);

    queues.erase(std::remove_if(queues.begin(), queues.end(),
            [](const PlayerQueue& p) {return p.queueEnded;}), queues.end());
}

这就是我当前渲染音频数据的方式,我在每个OggPlayerPlayerQueue 向量中寻找,如果它们还没有到达PCM 数据数组的末尾,则通过传递数组的指针使它们渲染音频数据。我在完成音频数据后平滑音频数据,以防止剪辑或其他事情。如果队列完成了音频渲染(完全),则最后从向量中删除队列。

void PlayerQueue::renderAudio(float * audioData, int32_t numFrames, bool isStreamStereo) {
    if(isStreamStereo) {
        renderStereo(audioData, numFrames);
    } else {
        renderMono(audioData, numFrames);
    }
}

void PlayerQueue::renderStereo(float *audioData, int32_t numFrames) {
    for(int i = 0; i < numFrames; i++) {
        if(player->isStereo) {
            if((int) ((float) (offset + i) * pitch) * 2 + 1 < player->data.size()) {
                float left = player->data.at((int)((float) (offset + i) * pitch) * 2);
                float right = player->data.at((int)((float) (offset + i) * pitch)  * 2 + 1);

                if(pan < 0) {
                    audioData[i * 2] += (left + right * (float) sin(abs(pan) * M_PI / 2.0)) * (float) playScale;
                    audioData[i * 2 + 1] += right * (float) cos(abs(pan) * M_PI / 2.0) * (float) playScale;
                } else {
                    audioData[i * 2] += left * (float) cos(pan * M_PI / 2.0) * (float) playScale;
                    audioData[i * 2 + 1] += (right + left * (float) sin(pan * M_PI / 2.0)) * (float) playScale;
                }
            } else {
                break;
            }
        } else {
            if((int) ((float) (offset + i) * pitch) < player->data.size()) {
                float sample = player->data.at((int) ((float) (offset + i) * pitch));

                if(pan < 0) {
                    audioData[i * 2] += sample * (1 + (float) sin(abs(pan) * M_PI / 2.0)) * (float) playScale;
                    audioData[i * 2 + 1] += sample * (float) cos(abs(pan) * M_PI / 2.0) * (float) playScale;
                } else {
                    audioData[i * 2] += sample * (float) cos(pan * M_PI / 2.0) * (float) playScale;
                    audioData[i * 2 + 1] += sample * (1 + (float) sin(pan * M_PI / 2.0)) * (float) playScale;
                }
            } else {
                break;
            }
        }
    }

    offset += numFrames;

    if((float) offset * pitch >= player->data.size()) {
        offset = 0;
        queueEnded = true;
    }
}

void PlayerQueue::renderMono(float *audioData, int32_t numFrames) {
    for(int i = 0; i < numFrames; i++) {
        if(player->isStereo) {
            if((int) ((float) (offset + i) * pitch) * 2 + 1 < player->data.size()) {
                audioData[i] += (player->data.at((int) ((float) (offset + i) * pitch) * 2) + player->data.at((int) ((float) (offset + i) * pitch) * 2 + 1)) / 2 * (float) playScale;
            } else {
                break;
            }
        } else {
            if((int) ((float) (offset + i) * pitch) < player->data.size()) {
                audioData[i] += player->data.at((int) ((float) (offset + i) * pitch)) * (float) playScale;
            } else {
                break;
            }
        }

        if(audioData[i] > 1.0)
            audioData[i] = 1.0;
        else if(audioData[i] < -1.0)
            audioData[i] = -1.0;
    }

    offset += numFrames;

    if((float) offset * pitch >= player->data.size()) {
        queueEnded = true;
        offset = 0;
    }
}

我在一个会话中渲染队列的所有内容(平移、播放、缩放),同时考虑扬声器和音频文件的状态(单声道或立体声)

using namespace oboe;

class OggPianoEngine : public AudioStreamCallback {
public:
    void initialize();
    void start(bool isStereo);
    void closeStream();
    void reopenStream();
    void release();

    bool isStreamOpened = false;
    bool isStreamStereo;

    int deviceSampleRate = 0;

    DataCallbackResult
    onAudioReady(AudioStream *audioStream, void *audioData, int32_t numFrames) override;
    void onErrorAfterClose(AudioStream *audioStream, Result result) override ;

    AudioStream* stream;
    std::vector<OggPlayer>* players;

    int addPlayer(std::vector<float> data, bool isStereo, int sampleRate) const;

    void addQueue(int id, float pan, float pitch, int playerScale) const;
};

最后在OggPianoEngine中,我放入了OggPlayer的向量,这样我的应用程序就可以在内存中保存多个声音,让用户可以添加声音,也可以随时随地播放。

DataCallbackResult
OggPianoEngine::onAudioReady(AudioStream *audioStream, void *audioData, int32_t numFrames) {
    for(int i = 0; i < players->size(); i++) {
        players->at(i).renderAudio(static_cast<float*>(audioData), numFrames, i == 0, audioStream->getChannelCount() != 1);
    }

    return DataCallbackResult::Continue;
}

在引擎中渲染音频很简单,正如你所料,我只是通过OggPlayer的向量寻找,然后调用renderAudio方法。下面的代码是我如何初始化AudioStream

void OggPianoEngine::start(bool isStereo) {
    AudioStreamBuilder builder;

    builder.setFormat(AudioFormat::Float);
    builder.setDirection(Direction::Output);
    builder.setChannelCount(isStereo ? ChannelCount::Stereo : ChannelCount::Mono);
    builder.setPerformanceMode(PerformanceMode::LowLatency);
    builder.setSharingMode(SharingMode::Exclusive);

    builder.setCallback(this);

    builder.openStream(&stream);

    stream->setBufferSizeInFrames(stream->getFramesPerBurst() * 2);

    stream->requestStart();

    deviceSampleRate = stream->getSampleRate();

    isStreamOpened = true;
    isStreamStereo = isStereo;
}

由于我看了像thisthis 这样的双簧管基本视频指南,所以我尝试配置LowLatency 模式的基本设置(例如,将设置缓冲区大小设置为突发大小乘以2)。但是当队列太多时,音频开始停止渲染。起初,声音开始结结巴巴。感觉就像它跳过了一些渲染会话,然后如果我在此之后再次点击播放按钮,它会完全停止渲染。我等了一会儿(5~10秒,有足够的时间等待队列被清空)后它再次开始渲染。所以我有几个问题

  1. 如果像上述情况那样渲染音频需要太多时间,双簧管是否会停止渲染音频?
  2. 我是否达到了渲染音频的限制,这意味着仅限制队列数量是解决方案?或者有什么方法可以达到更好的性能?

这些代码在我的flutter插件中,所以你可以从这个github链接获得完整的代码

【问题讨论】:

    标签: android c++ audio oboe


    【解决方案1】:

    如果像上述情况那样渲染音频需要太多时间,双簧管是否会停止渲染音频?

    是的。如果您阻止onAudioReady 的时间超过numFrames 表示的时间,您将收到音频故障。我敢打赌,如果您运行 systrace.py --time=5 -o trace.html -a your.app.packagename audio sched freq,您会发现您在该方法中花费了太多时间。

    我是否达到了渲染音频的限制,这意味着只有限制队列数量才是解决方案?或者有什么方法可以达到更好的性能?

    看起来像。问题是你试图在音频回调中做太多的工作。我会立即尝试的事情:

    • 不同的编译器优化:尝试-O2-O3-Ofast
    • 分析回调中的代码 - 确定您花费最多时间的地方。
    • 您似乎向sincos 打了很多电话。这些函数可能有更快的版本。

    我谈到了其中一些调试/优化技术in this talk

    另一个快速提示。尽量避免使用原始指针,除非你真的别无选择。例如AudioStream* stream; 会更好,因为std::shared_ptr&lt;AudioStream&gt;std::vector&lt;OggPlayer&gt;* players; 可以重构为std::vector&lt;OggPlayer&gt; players;

    【讨论】:

    • 感谢您的快速回答和丰富的提示!我会尽量优化我的代码,再次感谢
    猜你喜欢
    • 2012-06-09
    • 2021-06-22
    • 2013-08-24
    • 2014-07-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-26
    相关资源
    最近更新 更多