You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RtAudio回调函数生成失真正弦波的原因排查与求助

问题

我写了一个RtAudio回调函数,用来给缓冲区填充指定频率和振幅的正弦波样本,同时计算下一个缓冲区的正弦波起始相位。但播放时生成的声音明显不是正弦波。

我已经打印了缓冲区的样本值,连续缓冲区之间的相位是正确的,没有不匹配的问题。测试了回调函数的耗时,填充缓冲区只需要不到1微秒。当前用的缓冲区大小是256,采样率44110,缓冲区播放时长是毫秒级。

注意:当我把缓冲区长度增大到秒级时,声音是符合预期的正弦波,但这种大小带来的延迟无法接受。

为什么这个RtAudio回调函数会生成失真的正弦波?我漏了什么操作或者哪里出错了?

int build_sound(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames,
               double streamTime, RtAudioStreamStatus status, void *userData) {
    
    double *buffer = (double *) outputBuffer;
    std::fill_n(buffer, nBufferFrames, 0.0);
    
    std::vector<pressedKey*> *data = static_cast<std::vector<pressedKey*>*>(userData);
    if (!data->empty()) { 
        for (auto it = data->begin(); it != data->end(); ) {
            pressedKey* trigger = *it;
            if (trigger->bufferphase.size()!=trigger->amps.size()) {
                trigger->bufferphase.resize(trigger->amps.size());
                for (int i=0; i<trigger->amps.size(); i++) {
                    trigger->bufferphase[i]=0;
                }
            }
            for (int i=0; i<trigger->amps.size(); i++) {
                for (int j=0; j<nBufferFrames; j=j+2)  {
                    double jdbl=static_cast<double>(j);
                    buffer[j]=buffer[j]+trigger->amps[i]*0.05*sin(2*PI*
                                                  trigger->freqs[i]*jdbl/SAMPLERATE
                                                  +trigger->bufferphase[i]);
                    buffer[j+1]=buffer[j+1]+trigger->amps[i]*0.05*sin(2*PI*
                                                    trigger->freqs[i]*jdbl/SAMPLERATE
                                                    +trigger->bufferphase[i]);
                }
                trigger->bufferphase[i]+=2*PI*(trigger->freqs[i]
                                              *(nBufferFrames-1)/SAMPLERATE); 
                if (i==0){
                    for (int j=0; j<nBufferFrames; j=j+2)  {
                        std::cout<<j<<" "<<buffer[j]<<std::endl;
                    }
                }  
            }
        ++it;       
        }
    }

    return 0;               
}

编辑:根据建议添加输出后,明确了问题来源:缓冲区之间存在一个缓冲区大小的间隔。我猜测启用交错模式时,RtAudio会把缓冲区大小设为nBufferSamples*2。

rtaudio问题波形图


分析与解决

你的代码存在两个核心错误,直接导致了正弦波失真:

  1. 相位更新计算错误
    你当前的相位增量代码用了nBufferFrames-1:
trigger->bufferphase[i]+=2*PI*(trigger->freqs[i]*(nBufferFrames-1)/SAMPLERATE); 

实际上每个缓冲区包含nBufferFrames完整帧,每帧对应一个采样时间增量,正确的相位增量应该是2*PI*freq*nBufferFrames/SAMPLERATE。少算最后一帧的偏移会导致下一个缓冲区的起始相位提前,波形出现断裂。

  1. 立体声交错模式的帧处理逻辑错误
    在RtAudio的交错模式中,nBufferFrames代表单声道的采样帧数,输出缓冲区总长度是nBufferFrames*声道数(这里是2)。你的循环for (j=0; j<nBufferFrames; j=j+2)完全搞错了逻辑:
  • 这个循环只会处理前nBufferFrames/2个立体声帧,剩下一半缓冲区样本未被填充,直接导致波形出现空白间隔,这就是你观察到的缓冲区之间的间隔问题。

修正后的核心代码片段

int build_sound(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames,
               double streamTime, RtAudioStreamStatus status, void *userData) {
    
    double *buffer = (double *) outputBuffer;
    std::fill_n(buffer, nBufferFrames * 2, 0.0); // 注意:缓冲区总长度是帧数量×声道数
    
    std::vector<pressedKey*> *data = static_cast<std::vector<pressedKey*>*>(userData);
    if (!data->empty()) { 
        for (auto* trigger : *data) { // 简化循环写法
            if (trigger->bufferphase.size() != trigger->amps.size()) {
                trigger->bufferphase.assign(trigger->amps.size(), 0.0); // 替代resize+循环赋值
            }
            for (int i=0; i<trigger->amps.size(); ++i) {
                for (int j=0; j<nBufferFrames; ++j) { // 遍历每个单声道帧
                    double time = static_cast<double>(j) / SAMPLERATE;
                    double sample = trigger->amps[i] * 0.05 * sin(2*PI*trigger->freqs[i]*time + trigger->bufferphase[i]);
                    buffer[j*2] += sample; // 左声道
                    buffer[j*2+1] += sample; // 右声道
                }
                // 修正相位增量计算
                trigger->bufferphase[i] += 2*PI * trigger->freqs[i] * nBufferFrames / SAMPLERATE;
                // 可选:将相位限制在0~2PI范围内,避免数值溢出
                trigger->bufferphase[i] = fmod(trigger->bufferphase[i], 2*PI);
            }
        }
    }

    return 0;               
}

另外,建议检查RtAudio的流初始化参数,确认声道数和交错模式的配置是否与代码逻辑匹配,避免缓冲区长度理解偏差。

内容的提问来源于stack exchange,提问作者Malcolm Regan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 17:22:00