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。

分析与解决
你的代码存在两个核心错误,直接导致了正弦波失真:
- 相位更新计算错误
你当前的相位增量代码用了nBufferFrames-1:
trigger->bufferphase[i]+=2*PI*(trigger->freqs[i]*(nBufferFrames-1)/SAMPLERATE);
实际上每个缓冲区包含nBufferFrames完整帧,每帧对应一个采样时间增量,正确的相位增量应该是2*PI*freq*nBufferFrames/SAMPLERATE。少算最后一帧的偏移会导致下一个缓冲区的起始相位提前,波形出现断裂。
- 立体声交错模式的帧处理逻辑错误
在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
相关产品推荐
相关产品推荐

