【发布时间】:2015-01-06 01:59:16
【问题描述】:
我目前正在编写一个 C++ 实时音频应用程序,它大致包含:
- 从缓冲区读取帧
- 用隐士插值法插入帧here
- 使用两个双二阶滤波器过滤每一帧(并每帧更新它们的系数)
- 一个包含 18 个双二阶计算的 3 波段交叉
- 来自 STK 库 here 的 FreeVerb 算法
我认为这对于我的 PC 来说应该是可以处理的,但我经常会遇到一些缓冲区下溢,所以我想提高我的应用程序的性能。我有一堆问题希望你能回答我。 :)
1) 运算符重载
而不是直接使用我的浮动样本并为每个样本进行计算,
我将我的浮点数打包在一个包含左右样本的Frame 类中。该类使用float 重载了一些用于加法、减法和乘法的运算符。
过滤器(主要是双二阶)和混响与浮点数一起使用,不使用此类,但 Hermite 插值器以及用于音量控制和混合的每个乘法和加法都使用该类。
这对性能有影响吗?直接使用左右样本会更好吗?
2) std::function
来自音频 IO 库 PortAudio 的回调函数调用 std::function。我用它来封装与 PortAudio 相关的所有内容。所以“用户”用 std::bind 设置了自己的回调函数
std::bind( &AudioController::processAudio,
&(*this),
std::placeholders::_1,
std::placeholders::_2));
由于对于每个回调,都必须从 CPU 中找到正确的函数(但这是可行的……),这是否会产生影响?定义一个用户必须继承的类会更好吗?
3) 虚函数
我使用了一个名为 AudioProcessor 的类,它声明了一个虚函数:
virtual void tick(Frame *buffer, int frameCout) = 0;
此函数总是一次处理多个帧。根据驱动器,每次调用 200 帧到 1000 帧。 在信号处理路径中,我从多个派生类中调用了这个函数 6 次。我记得这是通过查找表完成的,因此 CPU 确切地知道它必须调用哪个函数。那么调用“虚拟”(派生)函数的过程对性能有影响吗?
这样做的好处是源代码中的结构,但仅使用内联可能会提高性能。
这些都是现在的问题。我有更多关于 Qt 的事件循环的信息,因为我认为我的 GUI 也使用了相当多的 CPU 时间。但我猜这是另一个话题。 :)
提前致谢!
这些都是信号处理中的相关函数调用。其中一些来自 STK 库。 双二阶函数来自 STK,应该可以正常运行。这也适用于 freeverb 算法。
// ################################ AudioController Function ############################
void AudioController::processAudio(int frameCount, float *output) {
// CALCULATE LEFT TRACK
Frame * leftFrameBuffer = (Frame*) output;
if(leftLoaded) { // the left processor is loaded
leftProcessor->tick(leftFrameBuffer, frameCount); //(TrackProcessor::tick()
} else {
for(int i = 0; i < frameCount; i++) {
leftFrameBuffer[i].leftSample = 0.0f;
leftFrameBuffer[i].rightSample = 0.0f;
}
}
// CALCULATE RIGHT TRACk
if(rightLoaded) { // the right processor is loaded
// the rightFrameBuffer is allocated once and ensured to have enough space for frameCount Frames
rightProcessor->tick(rightFrameBuffer, frameCount); //(TrackProcessor::tick()
} else {
for(int i = 0; i < frameCount; i++) {
rightFrameBuffer[i].leftSample = 0.0f;
rightFrameBuffer[i].rightSample = 0.0f;
}
}
// MIX
for(int i = 0; i < frameCount; i++ ) {
leftFrameBuffer[i] = volume * (leftRightMix * leftFrameBuffer[i] + (1.0 - leftRightMix) * rightFrameBuffer[i]);
}
}
// ################################ AudioController Function ############################
void TrackProcessor::tick(Frame *frames, int frameNum) {
if(bufferLoaded && playback) {
for(int i = 0; i < frameNum; i++) {
// read from buffer
frames[i] = bufferPlayer->tick();
// filter coeffs
caltulateFilterCoeffs(lowCutoffFilter->tick(), highCutoffFilter->tick());
// filter
frames[i].leftSample = lpFilterL->tick(hpFilterL->tick(frames[i].leftSample));
frames[i].rightSample = lpFilterR->tick(hpFilterR->tick(frames[i].rightSample));
}
} else {
for(int i = 0; i < frameNum; i++) {
frames[i] = Frame(0,0);
}
}
// Effect 1, Equalizer
if(effsActive[0]) {
insEffProcessors[0]->tick(frames, frameNum);
}
// Effect 2, Reverb
if(effsActive[1]) {
insEffProcessors[1]->tick(frames, frameNum);
}
// Volume
for(int i = 0; i < frameNum; i++) {
frames[i].leftSample *= volume;
frames[i].rightSample *= volume;
}
}
// ################################ Equalizer ############################
void EqualizerProcessor::tick(Frame *frames, int frameNum) {
if(active) {
Frame lowCross;
Frame highCross;
for(int f = 0; f < frameNum; f++) {
lowAmp = lowAmpFilter->tick();
midAmp = midAmpFilter->tick();
highAmp = highAmpFilter->tick();
lowCross = highLPF->tick(frames[f]);
highCross = highHPF->tick(frames[f]);
frames[f] = lowAmp * lowLPF->tick(lowCross)
+ midAmp * lowHPF->tick(lowCross)
+ highAmp * lowAPF->tick(highCross);
}
}
}
// ################################ Reverb ############################
// This function just calls the stk::FreeVerb tick function for every frame
// The FreeVerb implementation can't realy be optimised so I will take it as it is.
void ReverbProcessor::tick(Frame *frames, int frameNum) {
if(active) {
for(int i = 0; i < frameNum; i++) {
frames[i].leftSample = reverb->tick(frames[i].leftSample, frames[i].rightSample);
frames[i].rightSample = reverb->lastOut(1);
}
}
}
// ################################ Buffer Playback (BufferPlayer) ############################
Frame BufferPlayer::tick() {
// adjust read position based on loop status
if(inLoop) {
while(readPos > loopEndPos) {
readPos = loopStartPos + (readPos - loopEndPos);
}
}
int x1 = readPos;
float t = readPos - x1;
Frame f = interpolate(buffer->frameAt(x1-1),
buffer->frameAt(x1),
buffer->frameAt(x1+1),
buffer->frameAt(x1+2),
t);
readPos += stepSize;;
return f;
}
// interpolation:
Frame BufferPlayer::interpolate(Frame x0, Frame x1, Frame x2, Frame x3, float t) {
Frame c0 = x1;
Frame c1 = 0.5f * (x2 - x0);
Frame c2 = x0 - (2.5f * x1) + (2.0f * x2) - (0.5f * x3);
Frame c3 = (0.5f * (x3 - x0)) + (1.5f * (x1 - x2));
return (((((c3 * t) + c2) * t) + c1) * t) + c0;
}
inline Frame BufferPlayer::frameAt(int pos) {
if(pos < 0) {
pos = 0;
} else if (pos >= frames) {
pos = frames -1;
}
// get chunk and relative Sample
int chunk = pos/ChunkSize;
int chunkSample = pos%ChunkSize;
return Frame(leftChunks[chunk][chunkSample], rightChunks[chunk][chunkSample]);
}
【问题讨论】:
-
这些语言结构不会成为瓶颈。很有可能您的音频处理速度很慢,或者您执行动态分配 (
new Frame) 之类的操作比您需要的频率高得多。 -
我没有在回调中分配任何东西,我使用现有的缓冲区。我想我会添加所有相关功能,以便您检查处理。 ;)
-
您应该始终在优化之前进行分析。这将有助于隔离需要性能优化的代码区域。
-
是否有部分代码可以并行执行?例如,一个线程处理左侧样本,另一个线程处理右侧样本。另外,看看您是否可以利用图形处理单元的处理能力。
-
是的,均衡器处理可以拆分为左右,但我不知道是否值得分配一个新线程。 (但是这会起作用......)
标签: c++ performance audio real-time