Search code examples
c++dynamic-memory-allocationportaudio

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


When I declare buffer as float buffer[FRAMES_PER_BUFFER][2], I get a nice sound out of PortAudio's Pa_WriteStream. When I declare buffer as a float ** and then dynamically allocate (and zero out) a block of memory for it, I get no sound or popping (be careful if you try this on your computer).

As far as I can tell the structure of block pointed to by buffer in each case is identical, but the memory location is different (stack vs heap). This can be seen by the output of the cout statements at the end of the attached example.

How can I dynamically allocate buffer? My application will not know FRAMES_BER_BUFFER or the constant 2 (could be 3 or 4) at compile time.

I'm running this code on Mac with PortAudio 19.5.0 and compiling with gnu (tried various versions).

#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; 

}

Buffer with static allocation

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
...

Buffer with dynamic allocation

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
...

Solution

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

    float buffer[FRAMES_PER_BUFFER][2]

    Just:

    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] = ...;
    }
    

    If you want to have an array of arrays of 2 floats, you could do:

    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] = ...;
    }
    

    This is not and is unrelated to float **. In your code, you are writing into Pa_WriteStream(..., buffer, ...), which writes float values into a region allocated with new float*[FRAMES_PER_BUFFER]; This overwrites some values written before with assignment buffer[i] = with floating point values written by Pa_WriteStream. This results in invalid pointer values beeing stored in the region allocated for buffer, which makes the subsequent access buffer[frame][0] = invalid - the memory region pointed to by buffer stores float values, not valid float * values.

    If you want, you can pass the region allocated with temp (or buffer[0], as they point to the same region) to Pa_WriteStream and then access them using pointers allocated in buffer. This is unnecessary indirection and waste of memory - just access the memory you allocated directly, there is no reason to use an array of pointers to the region.

    // 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
    }