实时音频应用程序,提高性能
Realtime audio application, improving performance
我目前正在编写一个C++实时音频应用程序,它大致包含:
- 从缓冲区读取帧
- 在这里使用隐式插值对帧进行插值
- 用两个双四阶滤波器对每帧进行滤波(并每帧更新其系数)
- 一个包含18个双象限计算的3波段交叉
- STK库中的FreeVerb算法
我认为这应该适用于我的电脑,但我偶尔会遇到一些缓冲区下溢,所以我想提高我的应用程序的性能。我有一堆问题,希望你能回答我
1)操作员过载
不是直接使用我的flaot样本并对每个样本进行计算,我将浮点封装在一个Frame
类中,该类包含左侧和右侧的Sample。该类重载了一些用于float
的加法、减法和乘法的运算符。
滤波器(主要是双声道)和混响使用浮点,不使用此类,但hermite插值器以及用于音量控制和混音的每一次乘法和加法都使用该类。
这对性能有影响吗?直接使用左右样本会更好吗?
2)标准::函数
音频IO库PortAudio的回调函数调用std::函数。我用它来封装与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]);
}
关于性能改进的一些建议:
优化数据缓存使用率
查看对大量数据(例如数组)进行操作的函数。函数应该将数据加载到缓存中,对数据进行操作,然后存储回内存。
应将数据组织为最适合数据缓存的数据。如果数据不合适,就把它分解成更小的块。在网上搜索"数据驱动设计"answers"缓存优化"。
在一个项目中,执行数据平滑,我改变了数据的布局,获得了70%的性能。
使用多个线程
从全局来看,您可能能够使用至少三个专用线程:输入、处理和输出。输入线程获取数据并将其存储在缓冲区中;在Web上搜索"双重缓冲"。第二个线程从输入缓冲区获取数据,对其进行处理,然后写入输出缓冲区。第三个线程将数据从输出缓冲区写入文件。
您还可以从将线程用于左侧和右侧示例中获益。例如,当一个线程正在处理左样本时,另一个线程可能正在处理右样本。如果您可以将线程放在不同的核心上,您可能会看到更多的性能优势。
使用GPU处理
许多现代图形处理单元(GPU)都有许多可以处理浮点值的内核。也许你可以将一些过滤或分析功能委托给GPU中的核心。请注意,这需要开销,为了获得好处,处理部分应该比开销更具计算性。
减少分支
处理器更喜欢处理数据而不是分支。分支暂停执行,因为处理器必须弄清楚从哪里获取和处理下一条指令。有些具有可以包含小循环的大型指令缓存;但是再次分支到循环的顶部仍然会受到惩罚。请参见"循环展开"。还要检查编译器的优化,并优化高性能。如果情况正确,许多编译器会为您切换到循环展开。
减少处理量
你需要处理整个样品还是部分样品?例如,在视频处理中,大部分帧不会只改变很小的部分。所以不需要处理整个帧。音频通道是否可以隔离,以便只处理少数通道而不是整个频谱?
帮助编译器优化的编码
您可以使用const
修饰符来帮助编译器进行优化。编译器可能能够对不变的变量和不变的变量使用不同的算法。例如,const
值可以放在可执行代码中,但non-const
值必须放在内存中。
使用static
和const
也有帮助。static
通常只包含一个实例。const
意味着一些不会改变的东西。因此,如果变量只有一个实例不变,编译器可以将其放入可执行或只读内存中,并对代码进行更高的优化。
同时加载多个变量也会有所帮助。处理器可以将数据放入高速缓存。编译器可能能够使用专门的汇编指令来获取顺序数据。
- 试图在visual studio上用C++创建一个桌面应用程序
- FFmpeg:制作一个应用程序比直接使用ffmepg更好吗
- 在C应用程序中运行C++(带有STL)函数
- 使用VerQueryValue检索应用程序的文件描述
- Qt C++静态thread_local QNetworkAccessManager是线程应用程序的好选择吗
- 使用调试/崩溃报告将应用程序部署到客户端
- C++控制台应用程序阻止退出
- 码头化的C++应用程序是否向后兼容早期的内核版本
- 将应用程序从32位移植到64位时出现问题
- 如何改变c++应用程序的视觉效果
- WM_CTLCOLORSTATIC从未在WIN32应用程序中触发
- PC中的程序和PHONE中的本机描述应用程序之间的数据连接
- 应用程序崩溃并显示"symbol _ZdlPvm, version Qt_5 not defined in file libQt5Core.so.5 with link time reference"
- 示例外壳应用程序显示的 V8 "segmentation fault (core dumped)"错误
- phytec phyBOARD iMX-6在从闪存而不是SD卡运行qt5 opengles应用程序时表现不佳(FPS减半
- 为什么导入Mixed native/CLR lib.dll的本机C++应用程序没有在Mixed lib.dll中的外部变
- 如何从Windows应用程序输出到标准?
- C++应用程序 MySQL odbc 数据库连接错误:在引发"otl_tmpl_exception<>"实例后终止调用
- 如何在 64 位 vb.net Windows 应用程序中引用 32 位 dll
- 我应该使用Direct3D还是只使用Windows GDI来获得高级/高性能的Win32应用程序UI