如何在 C++ 中为 PortAudio Pa_WriteStream 动态分配缓冲区?

How do you dynamically allocate a buffer for PortAudio Pa_WriteStream in C++?

当我将 buffer 声明为 float buffer[FRAMES_PER_BUFFER][2] 时,我从 PortAudio 的 Pa_WriteStream 中得到了美妙的声音。当我将缓冲区声明为 float **,然后为其动态分配(并清零)一块内存时,我听不到任何声音或爆音(如果您在计算机上尝试此操作,请小心)。

据我所知,buffer 指向的块结构在每种情况下都是相同的,但内存位置不同(堆栈与堆)。这可以从所附示例末尾的 cout 语句的输出看出。

如何动态分配 buffer?我的应用程序在编译时不知道 FRAMES_BER_BUFFER 或常量 2(可能是 3 或 4)。

我 运行 此代码在 Mac 上使用 PortAudio 19.5.0 并使用 gnu 编译(尝试了各种版本)。

#include <iostream>
#include "math.h"
#include "portaudio.h"

#define FRAMES_PER_BUFFER 512
#define SAMPLE_RATE 44100
#define TS (1.0/SAMPLE_RATE)

#define CAREFULLY(_call_)                          \
    err = _call_;                                  \
    if ( err != paNoError ) {                      \
        std::cout << Pa_GetErrorText(err) << "\n"; \
        exit(1);                                   \
    }

int main(int argc, char * argv[]) {

    PaStreamParameters outputParameters; 
    PaStream *stream;
    PaError err;

    CAREFULLY(Pa_Initialize());

    outputParameters.device = Pa_GetDefaultOutputDevice();
    outputParameters.channelCount = 2;
    outputParameters.sampleFormat = paFloat32;
    outputParameters.suggestedLatency = 0.00;
    outputParameters.hostApiSpecificStreamInfo = NULL;

    CAREFULLY(Pa_OpenStream(
              &stream,
              NULL,
              &outputParameters,
              SAMPLE_RATE,
              FRAMES_PER_BUFFER,
              paClipOff,
              NULL,
              NULL)); 

    CAREFULLY(Pa_StartStream(stream));

    // Use this initialization of buffer to make a pretty sound
    float buffer[FRAMES_PER_BUFFER][2];

    // Use this initialization of buffer to get either no sound or popping sounds
    // What should go here instead?
    // float * temp = new float[FRAMES_PER_BUFFER * 2];    
    // float ** buffer = new float*[FRAMES_PER_BUFFER];    
    // for (int i = 0; i < FRAMES_PER_BUFFER; i++) {
    //     buffer[i] = (temp + i * 2);
    //     for ( int j=0; j<2; j++) {
    //         buffer[i][j] = 0.0;
    //     }
    // }

    int frame = 0;
    for ( float t=0; t<1; t += TS ) {
      if ( frame >= FRAMES_PER_BUFFER ) {
        CAREFULLY(Pa_WriteStream(stream, buffer, FRAMES_PER_BUFFER));
        frame = 0;
      }
      buffer[frame][0] = sin(2*M_PI*440*t);
      buffer[frame][1] = sin(2*M_PI*441*t);      
      frame++;     

    }

    // Show addresses and contents 
    for ( int i=0; i<10; i++  ) {
        for ( int j=0; j<2; j++ )
            std::cout << &buffer[i][j] << "\t" << buffer[i][j] << "\t";
        std::cout << "\n";
    }    

    return 0; 

}

带静态分配的缓冲区

0x7ffee3cb5440  0.556439        0x7ffee3cb5444  0.545642
0x7ffee3cb5448  0.607343        0x7ffee3cb544c  0.597127
0x7ffee3cb5450  0.655866        0x7ffee3cb5454  0.646261
0x7ffee3cb5458  0.701818        0x7ffee3cb545c  0.692851
0x7ffee3cb5460  0.745020        0x7ffee3cb5464  0.736712
0x7ffee3cb5468  0.785301        0x7ffee3cb546c  0.777672
0x7ffee3cb5470  0.822504        0x7ffee3cb5474  0.815571
0x7ffee3cb5478  0.856483        0x7ffee3cb547c  0.850258
0x7ffee3cb5480  0.887105        0x7ffee3cb5484  0.881597
0x7ffee3cb5488  0.914250        0x7ffee3cb548c  0.909465
...

动态分配缓冲区

0x7f816e00de00  0.556439        0x7f816e00de04  0.545642
0x7f816e00de08  0.607343        0x7f816e00de0c  0.597127
0x7f816e00de10  0.655866        0x7f816e00de14  0.646261
0x7f816e00de18  0.701818        0x7f816e00de1c  0.692851
0x7f816e00de20  0.745020        0x7f816e00de24  0.736712
0x7f816e00de28  0.785301        0x7f816e00de2c  0.777672
0x7f816e00de30  0.822504        0x7f816e00de34  0.815571
0x7f816e00de38  0.856483        0x7f816e00de3c  0.850258
0x7f816e00de40  0.887105        0x7f816e00de44  0.881597
0x7f816e00de48  0.914250        0x7f816e00de4c  0.909465
...

How do you dynamically allocate a buffer for PortAudio Pa_WriteStream in C++?

float buffer[FRAMES_PER_BUFFER][2]

刚刚:

float *buffer = malloc(sizeof(*buffer) * FRAMES_PER_BUFFER * 2);
// or in c++:     
float *buffer = new float[FRAMES_PER_BUFFER * 2];

for (...) {
  Pa_WriteStream(..., buffer, FRAMES_PER_BUFFER);
  buffer[frame * 2 + 0] = ...;
  buffer[frame * 2 + 1] = ...;
}

如果你想要一个由 2 个浮点数组成的数组,你可以这样做:

float (*buffer)[2] = malloc(sizeof(*buffer) * FRAMES_PER_BUFFER);
// or in c++:
float (*buffer)[2] = new float[FRAMES_PER_BUFFER][2];

for (...) {
  Pa_WriteStream(..., buffer, FRAMES_PER_BUFFER);
  buffer[frame][0] = ...;
  buffer[frame][1] = ...;
}

不是 并且与 float ** 无关。在您的代码中,您正在写入 Pa_WriteStream(..., buffer, ...),它将浮点值写入分配有 new float*[FRAMES_PER_BUFFER]; 的区域。这 覆盖 之前使用分配 buffer[i] = 写入的一些值] 与 Pa_WriteStream 写入的浮点值。这导致无效的 pointer 值存储在为 buffer 分配的区域中,这使得后续访问 buffer[frame][0] = 无效 - [=19 指向的内存区域=] 存储 float 个值,无效 float * 个值。

如果需要,您可以将temp分配的区域(或buffer[0],因为它们指向到同一区域)传递给Pa_WriteStream 然后使用在 buffer 中分配的指针访问它们。这是不必要的间接寻址和内存浪费 - 直接访问您分配的内存,没有理由使用指向该区域的指针数组。

// with the original code uncommented with 
float * temp = new float[FRAMES_PER_BUFFER * 2];    
float ** buffer = new float*[FRAMES_PER_BUFFER];    
for (int i = 0; i < FRAMES_PER_BUFFER; i++) {
     // every element in buffer _points_ to an element in temp
     buffer[i] = temp + i * 2;
     // I would prefer buffer[i] = &temp[i + 2];
}

for (...) {
  Pa_WriteStream(..., temp, FRAMES_PER_BUFFER);
  // because buffer[0] == temp , the above does exactly the same as:
  Pa_WriteStream(..., buffer[0], FRAMES_PER_BUFFER);


  buffer[frame][0] = ...;
  buffer[frame][1] = ...;
  // because buffer[i] == temp + i * 2 , the above does the same as:
  temp[frame * 2 + 0] = ...;
  temp[frame * 2 + 0] = ...;
  // so buffer can be removed and is unnecessary
}